aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorsamalin <samalin@google.com>2020-12-26 01:08:36 +0800
committerMeng Wang <mewan@google.com>2021-01-15 17:32:59 -0800
commitb7eabb05450b9e382c03cf3fee3a79ae9f90072b (patch)
treef837a3154781d2c467a1ca8568ae268ca8b156bd
parente2877642062a4ee7ef20219755b9fa3bae39eba7 (diff)
downloadservice_entitlement-b7eabb05450b9e382c03cf3fee3a79ae9f90072b.tar.gz
Add implementations for service-entitlement lib
Implements the APIs for retrieving the VoWifi entitlement status through EAP-AKA authenticate method which defines in GSMA TS.43 specification. Bug: 173450048 Test: presubmit Change-Id: I7549e2a0287c1294b71a83c8189109f708e5b31f
-rw-r--r--Android.bp6
-rw-r--r--java/com/android/libraries/entitlement/CarrierConfig.java51
-rw-r--r--java/com/android/libraries/entitlement/EsimOdsaOperation.java292
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlement.java144
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlementException.java77
-rw-r--r--java/com/android/libraries/entitlement/ServiceEntitlementRequest.java199
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaApi.java236
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java370
-rw-r--r--java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java151
-rw-r--r--java/com/android/libraries/entitlement/eapaka/MasterKey.java391
-rw-r--r--java/com/android/libraries/entitlement/http/HttpClient.java170
-rw-r--r--java/com/android/libraries/entitlement/http/HttpConstants.java41
-rw-r--r--java/com/android/libraries/entitlement/http/HttpRequest.java80
-rw-r--r--java/com/android/libraries/entitlement/http/HttpResponse.java74
-rw-r--r--java/com/android/libraries/entitlement/utils/BytesConverter.java60
-rw-r--r--java/com/android/libraries/entitlement/utils/StreamUtils.java54
-rw-r--r--java/com/google/android/libraries/entitlement/CarrierData.java35
-rw-r--r--java/com/google/android/libraries/entitlement/EsimOdsaOperation.java288
-rw-r--r--java/com/google/android/libraries/entitlement/ServiceEntitlement.java122
-rw-r--r--java/com/google/android/libraries/entitlement/ServiceEntitlementException.java66
-rw-r--r--java/com/google/android/libraries/entitlement/ServiceEntitlementRequest.java176
21 files changed, 2395 insertions, 688 deletions
diff --git a/Android.bp b/Android.bp
index 4e110e7..42bcc38 100644
--- a/Android.bp
+++ b/Android.bp
@@ -20,9 +20,13 @@ java_library {
"java/**/*.java",
],
libs: [
+ "androidx.annotation_annotation",
"auto_value_annotations",
],
+ static_libs: [
+ "guava",
+ ],
plugins: ["auto_value_plugin"],
sdk_version: "current",
- min_sdk_version: "30",
+ min_sdk_version: "29",
}
diff --git a/java/com/android/libraries/entitlement/CarrierConfig.java b/java/com/android/libraries/entitlement/CarrierConfig.java
new file mode 100644
index 0000000..8f040c1
--- /dev/null
+++ b/java/com/android/libraries/entitlement/CarrierConfig.java
@@ -0,0 +1,51 @@
+/*
+ * 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;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Carrier specific customization to be used in the service entitlement queries and operations.
+ *
+ * @see #ServiceEntitlement
+ */
+@AutoValue
+public abstract class CarrierConfig {
+ /**
+ * Returns the carrier's entitlement server URL. If not set, will use {@code
+ * https://aes.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org} as defined in GSMA spec TS.43 section 2.1.
+ */
+ public abstract String serverUrl();
+
+ /** Returns a new {@link Builder} object. */
+ public static Builder builder() {
+ return new AutoValue_CarrierConfig.Builder()
+ .setServerUrl("");
+ }
+
+ /** Builder. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ public abstract CarrierConfig build();
+
+ /**
+ * Set's the carrier's entitlement server URL. If not set, will use {@code
+ * https://aes.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org} as defined in GSMA spec TS.43 section 2.1.
+ */
+ public abstract Builder setServerUrl(String url);
+ }
+}
diff --git a/java/com/android/libraries/entitlement/EsimOdsaOperation.java b/java/com/android/libraries/entitlement/EsimOdsaOperation.java
new file mode 100644
index 0000000..d7d23ac
--- /dev/null
+++ b/java/com/android/libraries/entitlement/EsimOdsaOperation.java
@@ -0,0 +1,292 @@
+/*
+ * 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;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * HTTP request parameters specific to on device service actiavation (ODSA). See GSMA spec TS.43
+ * section 6.2.
+ */
+@AutoValue
+public abstract class EsimOdsaOperation {
+ /** OSDA operation: CheckEligibility. */
+ public static final String OPERATION_CHECK_ELIGIBILITY = "CheckEligibility";
+ /** OSDA operation: ManageSubscription. */
+ public static final String OPERATION_MANAGE_SUBSCRIPTION = "ManageSubscription";
+ /** OSDA operation: ManageService. */
+ public static final String OPERATION_MANAGE_SERVICE = "ManageService";
+ /** OSDA operation: AcquireConfiguration. */
+ public static final String OPERATION_ACQUIRE_CONFIGURATION = "AcquireConfiguration";
+
+ /** Indicates that operation_type is not set. */
+ static final int OPERATION_TYPE_NOT_SET = -1;
+ /** To activate a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
+ public static final int OPERATION_TYPE_SUBSCRIBE = 0;
+ /** To cancel a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
+ public static final int OPERATION_TYPE_UNSUBSCRIBE = 1;
+ /** To manage an existing subscription, for {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
+ public static final int OPERATION_TYPE_CHANGE_SUBSCRIPTION = 2;
+ /**
+ * To transfer a subscription from an existing device, used by {@link
+ * #OPERATION_MANAGE_SUBSCRIPTION}.
+ */
+ public static final int OPERATION_TYPE_TRANSFER_SUBSCRIPTION = 3;
+ /**
+ * To inform the network of a subscription update, used by
+ * {@link #OPERATION_MANAGE_SUBSCRIPTION}.
+ */
+ public static final int OPERATION_TYPE_UPDATE_SUBSCRIPTION = 4;
+ /** To activate a service, used by {@link #OPERATION_MANAGE_SERVICE}. */
+ public static final int OPERATION_TYPE_ACTIVATE_SERVICE = 10;
+ /** To deactivate a service, used by {@link #OPERATION_MANAGE_SERVICE}. */
+ public static final int OPERATION_TYPE_DEACTIVATE_SERVICE = 11;
+
+ /** Indicates the companion device carries the same MSISDN as the primary device. */
+ public static final String COMPANION_SERVICE_SHAERED_NUMBER = "SharedNumber";
+ /** Indicates the companion device carries a different MSISDN as the primary device. */
+ public static final String COMPANION_SERVICE_DIFFERENT_NUMBER = "DiffNumber";
+
+ /** Returns the eSIM ODSA operation. Used by HTTP parameter "operation". */
+ public abstract String operation();
+
+ /**
+ * Returns the detiled type of the eSIM ODSA operation. Used by HTTP parameter "operation_type".
+ */
+ public abstract int operationType();
+
+ /**
+ * Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * "companion_terminal_id".
+ */
+ public abstract String companionTerminalId();
+
+ /**
+ * Returns the OEM of the companion device. Used by HTTP parameter "companion_terminal_vendor".
+ */
+ public abstract String companionTerminalVendor();
+
+ /**
+ * Returns the model of the companion device. Used by HTTP parameter "companion_terminal_model".
+ */
+ public abstract String companionTerminalModel();
+
+ /**
+ * Returns the software version of the companion device. Used by HTTP parameter
+ * "companion_terminal_sw_version".
+ */
+ public abstract String companionTerminalSoftwareVersion();
+
+ /**
+ * Returns the user-friendly version of the companion device. Used by HTTP parameter
+ * "companion_terminal_friendly_name".
+ */
+ public abstract String companionTerminalFriendlyName();
+
+ /**
+ * Returns the service type of the companion device, e.g. if the MSISDN is same as the primary
+ * device. Used by HTTP parameter "companion_terminal_service".
+ */
+ public abstract String companionTerminalService();
+
+ /**
+ * Returns the ICCID of the companion device. Used by HTTP parameter "companion_terminal_iccid".
+ */
+ public abstract String companionTerminalIccid();
+
+ /**
+ * Returns the ICCID of the companion device. Used by HTTP parameter "companion_terminal_iccid".
+ */
+ public abstract String companionTerminalEid();
+
+ /** Returns the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_eid". */
+ public abstract String terminalIccid();
+
+ /**
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * "terminal_eid".
+ */
+ public abstract String terminalEid();
+
+ /**
+ * Returns the unique identifier of the primary device eSIM, like the IMEI associated with the
+ * eSIM. Used by HTTP parameter "target_terminal_id".
+ */
+ public abstract String targetTerminalId();
+
+ /** Returns the ICCID primary device eSIM. Used by HTTP parameter "target_terminal_iccid". */
+ public abstract String targetTerminalIccid();
+
+ /**
+ * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * "target_terminal_eid".
+ */
+ public abstract String targetTerminalEid();
+
+ /** Returns a new {@link Builder} object. */
+ public static Builder builder() {
+ return new AutoValue_EsimOdsaOperation.Builder().setOperationType(OPERATION_TYPE_NOT_SET);
+ }
+
+ /**
+ * Builder.
+ *
+ * <p>For ODSA, the rule of which parameters are required varies or each
+ * operation/opeation_type.
+ * The Javadoc below gives high-level description, but please refer to GMSA spec TS.43 section
+ * 6.2
+ * for details.
+ */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the eSIM ODSA operation. Used by HTTP parameter "operation".
+ *
+ * <p>Required.
+ *
+ * @see #OPERATION_CHECK_ELIGIBILITY
+ * @see #OPERATION_MANAGE_SUBSCRIPTION
+ * @see #OPERATION_MANAGE_SERVICE
+ * @see #OPERATION_ACQUIRE_CONFIGURATION
+ */
+ public abstract Builder setOperation(String value);
+
+ /**
+ * Sets the detiled type of the eSIM ODSA operation. Used by HTTP parameter "operation_type"
+ * if set.
+ *
+ * <p>Required by some operation.
+ *
+ * @see #OPERATION_TYPE_SUBSCRIBE
+ * @see #OPERATION_TYPE_UNSUBSCRIBE
+ * @see #OPERATION_TYPE_CHANGE_SUBSCRIPTION
+ * @see #OPERATION_TYPE_TRANSFER_SUBSCRIPTION
+ * @see #OPERATION_TYPE_UPDATE_SUBSCRIPTION
+ * @see #OPERATION_TYPE_ACTIVATE_SERVICE
+ * @see #OPERATION_TYPE_DEACTIVATE_SERVICE
+ */
+ public abstract Builder setOperationType(int value);
+
+ /**
+ * Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
+ * "companion_terminal_id" if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ */
+ public abstract Builder setCompanionTerminalId(String value);
+
+ /**
+ * Sets the OEM of the companion device. Used by HTTP parameter "companion_terminal_vendor"
+ * if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ */
+ public abstract Builder setCompanionTerminalVendor(String value);
+
+ /**
+ * Sets the model of the companion device. Used by HTTP parameter "companion_terminal_model"
+ * if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ */
+ public abstract Builder setCompanionTerminalModel(String value);
+
+ /**
+ * Sets the software version of the companion device. Used by HTTP parameter
+ * "companion_terminal_sw_version" if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ */
+ public abstract Builder setCompanionTerminalSoftwareVersion(String value);
+
+ /**
+ * Sets the user-friendly version of the companion device. Used by HTTP parameter
+ * "companion_terminal_friendly_name" if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ */
+ public abstract Builder setCompanionTerminalFriendlyName(String value);
+
+ /**
+ * Sets the service type of the companion device, e.g. if the MSISDN is same as the primary
+ * device. Used by HTTP parameter "companion_terminal_service" if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ *
+ * @see #COMPANION_SERVICE_SHAERED_NUMBER
+ * @see #COMPANION_SERVICE_DIFFERENT_NUMBER
+ */
+ public abstract Builder setCompanionTerminalService(String value);
+
+ /**
+ * Sets the ICCID of the companion device. Used by HTTP parameter "companion_terminal_iccid"
+ * if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ */
+ public abstract Builder setCompanionTerminalIccid(String value);
+
+ /**
+ * Sets the eUICC identifier (EID) of the companion device. Used by HTTP parameter
+ * "companion_terminal_eid" if set.
+ *
+ * <p>Used by companion device ODSA operation.
+ */
+ public abstract Builder setCompanionTerminalEid(String value);
+
+ /**
+ * Sets the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_eid" if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ */
+ public abstract Builder setTerminalIccid(String value);
+
+ /**
+ * Sets the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * "terminal_eid" if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ */
+ public abstract Builder setTerminalEid(String value);
+
+ /**
+ * Sets the unique identifier of the primary device eSIM, like the IMEI associated with the
+ * eSIM. Used by HTTP parameter "target_terminal_id" if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ */
+ public abstract Builder setTargetTerminalId(String value);
+
+ /**
+ * Sets the ICCID primary device eSIM. Used by HTTP parameter "target_terminal_iccid" if
+ * set.
+ *
+ * <p>Used by primary device ODSA operation.
+ */
+ public abstract Builder setTargetTerminalIccid(String value);
+
+ /**
+ * Sets the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
+ * "target_terminal_eid" if set.
+ *
+ * <p>Used by primary device ODSA operation.
+ */
+ public abstract Builder setTargetTerminalEid(String value);
+
+ public abstract EsimOdsaOperation build();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlement.java b/java/com/android/libraries/entitlement/ServiceEntitlement.java
new file mode 100644
index 0000000..c196783
--- /dev/null
+++ b/java/com/android/libraries/entitlement/ServiceEntitlement.java
@@ -0,0 +1,144 @@
+/*
+ * 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;
+
+import android.content.Context;
+
+import com.android.libraries.entitlement.eapaka.EapAkaApi;
+
+import java.util.List;
+
+import androidx.annotation.Nullable;
+
+/**
+ * Implemnets protocol for carrier service entitlement configuration query and operation, based on
+ * GSMA TS.43 spec.
+ */
+public class ServiceEntitlement {
+ /** App ID for Voice-Over-LTE entitlement. */
+ public static final String APP_VOLTE = "ap2003";
+ /** App ID for Voice-Over-WiFi entitlement. */
+ public static final String APP_VOWIFI = "ap2004";
+ /** App ID for SMS-Over-IP entitlement. */
+ public static final String APP_SMSOIP = "ap2005";
+ /** App ID for on device service activation (OSDA) for companion device. */
+ public static final String APP_ODSA_COMPANION = "ap2006";
+ /** App ID for on device service activation (OSDA) for primary device. */
+ public static final String APP_ODSA_PRIMARY = "ap2009";
+
+ private final Context context;
+ private final int simSubscriptionId;
+ private final CarrierConfig carrierConfig;
+
+ /**
+ * Creates an instance for service entitlement configuration query and operation for the
+ * carrier.
+ *
+ * @param context context of application
+ * @param carrierConfig carrier specific configs used in the queries and operations.
+ * @param simSubscriptionId the subscroption ID of the carrier's SIM on device. This indicates
+ * which SIM to retrieve IMEI/IMSI from and perform EAP-AKA
+ * authentication with. See {@link
+ * android.telephony.SubscriptionManager} for how to get the
+ * subscroption ID.
+ */
+ public ServiceEntitlement(Context context, CarrierConfig carrierConfig, int simSubscriptionId) {
+ this.context = context;
+ this.simSubscriptionId = simSubscriptionId;
+ this.carrierConfig = carrierConfig;
+ }
+
+ /**
+ * Retrieves service entitlement configuration. For on device service activation (ODSA) of eSIM
+ * for companion/primary devices, use {@link #performEsimOdsa} instead.
+ *
+ * <p>Supported {@code appId}: {@link #APP_VOLTE}, {@link #APP_VOWIFI}, {@link #APP_SMSOIP}.
+ *
+ * <p>This method sends an HTTP GET request to entitlement server, responds to EAP-AKA
+ * challenge
+ * if needed, and returns the raw configuration doc as a string. The following parameters are
+ * set
+ * in the HTTP request:
+ *
+ * <ul>
+ * <li>"app": {@code appId}
+ * <li>"vers": 0, or {@code request.configurationVersion()} if it's not 0.
+ * <li>"entitlement_version": "2.0", or {@code request.entitlementVersion()} if it's not empty.
+ * <li>"token": not set, or {@code request.authenticationToken()} if it's not empty.
+ * <li>"IMSI": if "token" is set, set to {@link android.telephony.TelephonyManager#getImei}.
+ * <li>"EAP_ID": if "token" is not set, set this parameter to trigger embedded EAP-AKA
+ * authentication as decribed in TS.43 section 2.6.1. Its value is derived from IMSI as per
+ * GSMA spec RCC.14 section C.2.
+ * <li>"terminal_id": IMEI, or {@code request.terminalId()} if it's not empty.
+ * <li>"terminal_vendor": {@link android.os.Build#MANUFACTURER}, or {@code
+ * request.terminalVendor()} if it's not empty.
+ * <li>"terminal_model": {@link android.os.Build#MODEL}, or {@code request.terminalModel()} if
+ * it's not empty.
+ * <li>"terminal_sw_version": {@llink android.os.Build.VERSION#BASE_OS}, or {@code
+ * request.terminalSoftwareVersion()} if it's not empty.
+ * <li>"app_name": not set, or {@code request.appName()} if it's not empty.
+ * <li>"app_version": not set, or {@code request.appVersion()} if it's not empty.
+ * <li>"notif_token": not set, or {@code request.notificationToken()} if it's not empty.
+ * <li>"notif_action": {@code request.notificationAction()} if "notif_token" is set, otherwise
+ * not set.
+ * </ul>
+ *
+ * <p>Requires permission: READ_PRIVILEGED_PHONE_STATE, or carrier privilege.
+ *
+ * @param appId an app ID string defined in TS.43 section 2.2, e.g. {@link #APP_VOWIFI}.
+ * @param request contains parameters that can be used in the HTTP request.
+ */
+ @Nullable
+ public String queryEntitlementStatus(String appId, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ EapAkaApi eapAkaApi = new EapAkaApi(context, simSubscriptionId);
+ return eapAkaApi.queryEntitlementStatus(appId, carrierConfig.serverUrl(), request);
+ }
+
+ /**
+ * Retrieves service entitlement configurations for multiple app IDs in one HTTP
+ * request/response.
+ * For on device service activation (ODSA) of eSIM for companion/primary devices, use {@link
+ * #performEsimOdsa} instead.
+ *
+ * <p>Same with {@link #queryEntitlementStatus(String, ServiceEntitlementRequest)} except that
+ * multiple "app" parameters will be set in the HTTP request, in the order as they appear in
+ * parameter {@code appIds}.
+ */
+ public String queryEntitlementStatus(List<String> appIds, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ // TODO(b/177544547): Add implementation
+ return null;
+ }
+
+ /**
+ * Performs on device service activation (ODSA) of eSIM for companion/primary devices.
+ *
+ * <p>Supported {@code appId}: {@link #APP_ODSA_COMPANION}, {@link #APP_ODSA_PRIMARY}.
+ *
+ * <p>Similar to {@link #queryEntitlementStatus(String, ServiceEntitlementRequest)}, this method
+ * sends an HTTP GET request to entitlement server, responds to EAP-AKA challenge if needed, and
+ * returns the raw configuration doc as a string. Additional parameters from {@code operation}
+ * are set to the HTTP request. See {@link EsimOdsaOperation} for details.
+ */
+ public String performEsimOdsa(
+ String appId, ServiceEntitlementRequest request, EsimOdsaOperation operation)
+ throws ServiceEntitlementException {
+ // TODO(b/177544547): Add implementation
+ return null;
+ }
+}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementException.java b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
new file mode 100644
index 0000000..499a032
--- /dev/null
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementException.java
@@ -0,0 +1,77 @@
+/*
+ * 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;
+
+/** Indicates errors happened in retrieving service entitlement configuration. */
+public class ServiceEntitlementException extends Exception {
+ /** Unknown error. */
+ public static final int ERROR_UNKNOWN = 0;
+ /** Android telephony is unable to provide info like IMSI, e.g. when modem crashed. */
+ public static final int ERROR_PHONE_NOT_AVAILABLE = 1;
+ /**
+ * 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;
+ /**
+ * 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;
+ /**
+ * 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 ServiceEntitlementException(String message) {
+ // TODO(b/177544547): add implementation
+ }
+
+ public ServiceEntitlementException(
+ int error, int httpStatus, String retryAfter, String message, Throwable cause) {
+ // TODO(b/177544547): add implementation
+ }
+
+ /** Returns the error code, see {@link #ERROR_*}. */
+ public int getErrorCode() {
+ // TODO(b/177544547): add implementation
+ return ERROR_UNKNOWN;
+ }
+
+ /** Returns the HTTP status code returned by entitlement server; 0 if unavailable. */
+ public int getHttpStatus() {
+ // TODO(b/177544547): add implementation
+ return ERROR_SEVER_NOT_CONNECTABLE;
+ }
+
+ /**
+ * Returns the "Retry-After" header in HTTP response, often set with HTTP status code 503; an
+ * empty string if unavailable.
+ *
+ * @return the HTTP-date or a number of seconds to delay, as defiend in RFC 7231:
+ * https://tools.ietf.org/html/rfc7231#section-7.1.3
+ */
+ public String getRetryAfter() {
+ // TODO(b/177544547): add implementation
+ return null;
+ }
+}
diff --git a/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
new file mode 100644
index 0000000..6bbfee6
--- /dev/null
+++ b/java/com/android/libraries/entitlement/ServiceEntitlementRequest.java
@@ -0,0 +1,199 @@
+/*
+ * 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;
+
+import android.os.Build;
+import android.os.Build.VERSION;
+
+import com.google.auto.value.AutoValue;
+
+/** Service entitlement HTTP request parameters, as defiend in GSMA spec TS.43 section 2.2. */
+@AutoValue
+public abstract class ServiceEntitlementRequest {
+ /** Disables notification token. */
+ public static final int NOTICATION_ACTION_DISABLE = 0;
+ /** Enables FCM notification token. */
+ public static final int NOTICATION_ACTION_ENABLE_FCM = 2;
+
+ /**
+ * Returns the version of configuration currently stored on the client. Used by HTTP parameter
+ * "vers".
+ */
+ public abstract int configurationVersion();
+
+ /**
+ * Returns the version of the entitlement specification. Used by HTTP parameter
+ * "entitlement_version".
+ */
+ public abstract String entitlementVersion();
+
+ /** Returns the authentication token. Used by HTTP parameter "token". */
+ public abstract String authenticationToken();
+
+ /**
+ * Returns the unique identifier of the device like IMEI. Used by HTTP parameter "terminal_id".
+ */
+ public abstract String terminalId();
+
+ /** Returns the OEM of the device. Used by HTTP parameter "terminal_vendor". */
+ public abstract String terminalVendor();
+
+ /** Returns the model of the device. Used by HTTP parameter "terminal_model". */
+ public abstract String terminalModel();
+
+ /** Returns the software version of the device. Used by HTTP parameter "terminal_sw_version". */
+ public abstract String terminalSoftwareVersion();
+
+ /**
+ * Returns the name of the device application making the request. Used by HTTP parameter
+ * "app_name".
+ */
+ public abstract String appName();
+
+ /**
+ * Returns the version of the device application making the request. Used by HTTP parameter
+ * "app_version".
+ */
+ public abstract String appVersion();
+
+ /**
+ * Returns the FCM registration token used to register for entitlement configuration request
+ * from
+ * network. Used by HTTP parameter "notif_token".
+ */
+ public abstract String notificationToken();
+
+ /**
+ * Returns the action associated with the FCM registration token. Used by HTTP parameter
+ * "notif_action".
+ *
+ * @see #NOTICATION_ACTION_ENABLE_FCM
+ * @see #NOTICATION_ACTION_DISABLE
+ */
+ public abstract int notificationAction();
+
+ /** Returns a new {@link Builder} object. */
+ public static Builder builder() {
+ return new AutoValue_ServiceEntitlementRequest.Builder()
+ .setConfigurationVersion(0)
+ .setEntitlementVersion("2.0")
+ .setAuthenticationToken("")
+ .setTerminalId("")
+ .setTerminalVendor(Build.MANUFACTURER)
+ .setTerminalModel(Build.MODEL)
+ .setTerminalSoftwareVersion(VERSION.BASE_OS)
+ .setAppName("")
+ .setAppVersion("")
+ .setNotificationToken("")
+ .setNotificationAction(NOTICATION_ACTION_ENABLE_FCM);
+ }
+
+ /** Builder. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+ /**
+ * Sets the version of configuration currently stored on the client. Used by HTTP parameter
+ * "vers".
+ *
+ * <p>If not set, default to 0 indicating no existing configuration.
+ */
+ public abstract Builder setConfigurationVersion(int value);
+
+ /**
+ * Sets the current version of the entitlement specification. Used by HTTP parameter
+ * "entitlement_version".
+ *
+ * <p>If not set, default to "2.0" base on TS.43-v5.0.
+ */
+ public abstract Builder setEntitlementVersion(String value);
+
+ /**
+ * Sets the authentication token. Used by HTTP parameter "token".
+ *
+ * <p>If not set, will trigger embedded EAP-AKA authentication as decribed in TS.43 section
+ * 2.6.1.
+ */
+ public abstract Builder setAuthenticationToken(String value);
+
+ /**
+ * Sets the unique identifier of the device like IMEI. Used by HTTP parameter "terminal_id".
+ *
+ * <p>If not set, will use the device IMEI.
+ */
+ public abstract Builder setTerminalId(String value);
+
+ /**
+ * Sets the OEM of the device. Used by HTTP parameter "terminal_vendor".
+ *
+ * <p>If not set, will use {@link android.os.Build#MANUFACTURER}.
+ */
+ public abstract Builder setTerminalVendor(String value);
+
+ /**
+ * Sets the model of the device. Used by HTTP parameter "terminal_model".
+ *
+ * <p>If not set, will use {@link android.os.Build#MODEL}.
+ */
+ public abstract Builder setTerminalModel(String value);
+
+ /**
+ * Sets the software version of the device. Used by HTTP parameter "terminal_sw_version".
+ *
+ * <p>If not set, will use {@link android.os.Build.VERSION#BASE_OS}.
+ */
+ public abstract Builder setTerminalSoftwareVersion(String value);
+
+ /**
+ * Sets the name of the device application making the request. Used by HTTP parameter
+ * "app_name".
+ *
+ * <p>Optional.
+ */
+ public abstract Builder setAppName(String value);
+
+ /**
+ * Sets the version of the device application making the request. Used by HTTP parameter
+ * "app_version".
+ *
+ * <p>Optional.
+ */
+ public abstract Builder setAppVersion(String value);
+
+ /**
+ * Sets the FCM registration token used to register for entitlement configuration request
+ * from
+ * network. Used by HTTP parameter "notif_token".
+ *
+ * <p>Optional.
+ */
+ public abstract Builder setNotificationToken(String value);
+
+ /**
+ * Sets the action associated with the FCM registration token. Used by HTTP parameter
+ * "notif_action".
+ *
+ * <p>Required if a token is set with {@link #setNotificationToken}, and default to {@link
+ * #NOTICATION_ACTION_ENABLE_FCM}; otherwise ignored.
+ *
+ * @see #NOTICATION_ACTION_ENABLE_FCM
+ * @see #NOTICATION_ACTION_DISABLE
+ */
+ public abstract Builder setNotificationAction(int value);
+
+ public abstract ServiceEntitlementRequest build();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
new file mode 100644
index 0000000..898b782
--- /dev/null
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaApi.java
@@ -0,0 +1,236 @@
+/*
+ * 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 android.content.Context;
+import android.net.Uri;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Log;
+
+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;
+
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import androidx.annotation.Nullable;
+
+import com.google.common.net.HttpHeaders;
+
+import java.net.CookieHandler;
+import java.net.CookieManager;
+
+public class EapAkaApi {
+ private static final String TAG = "ServiceEntitlement";
+
+ public static final String EAP_CHALLENGE_RESPONSE = "eap-relay-packet";
+
+ /** Current version of the entitlement configuration. */
+ private static final String VERS = "vers";
+ /** Version of the entitlement configuration. */
+ private static final String ENTITLEMENT_VERSION = "entitlement_version";
+ /**
+ * Unique identifier for the device. Refer to {@link
+ * android.telephony.TelephonyManager#getImei()}.
+ */
+ private static final String TERMINAL_ID = "terminal_id";
+ /** Device manufacturer. */
+ private static final String TERMINAL_VENDOR = "terminal_vendor";
+ /** Device model. */
+ private static final String TERMINAL_MODEL = "terminal_model";
+ /** Device software version. */
+ private static final String TERMIAL_SW_VERSION = "terminal_sw_version";
+ /** Identifier for the requested entitlement. */
+ private static final String APP = "app";
+ /** NAI needed for EAP-AKA authentication. */
+ private static final String EAP_ID = "EAP_ID";
+
+ private static final String IMSI = "IMSI";
+ private static final String TOKEN = "token";
+ /** Action for the notification registration token. */
+ private static final String NOTIF_ACTION = "notif_action";
+ /** Attribute name of the notification registration token. */
+ private static final String NOTIF_TOKEN = "notif_token";
+ /** Attribute name of the app version. */
+ private static final String APP_VERSION = "app_version";
+ /** Attribute name of the app name. */
+ private static final String APP_NAME = "app_name";
+
+ private final Context context;
+ private final int simSubscriptionId;
+ private final HttpClient httpClient;
+
+ public EapAkaApi(Context context, int simSubscriptionId) {
+ this.context = context;
+ this.simSubscriptionId = simSubscriptionId;
+ this.httpClient = new HttpClient();
+ }
+
+ /**
+ * Retrieves raw entitlement configuration doc though EAP-AKA authentication.
+ *
+ * <p>Implementation based on GSMA TS.43-v5.0 2.6.1.
+ *
+ * @throws ServiceEntitlementException when getting an unexpected http response.
+ */
+ @Nullable
+ public String queryEntitlementStatus(
+ String appId, String serverUrl, ServiceEntitlementRequest request)
+ throws ServiceEntitlementException {
+ // TODO(b/177562073): localize cookie management instead of VM global CookieHandler
+ CookieHandler.setDefault(new CookieManager());
+
+ HttpRequest httpRequest =
+ HttpRequest.builder()
+ .setUrl(entitlementStatusUrl(appId, serverUrl, request))
+ .setRequestMethod(RequestMethod.GET)
+ .addRequestProperty(
+ HttpHeaders.ACCEPT,
+ "application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap"
+ + ".connectivity-xml")
+ .build();
+ HttpResponse response = httpClient.request(httpRequest);
+ if (response == null) {
+ throw new ServiceEntitlementException("Null http response");
+ }
+ if (response.contentType() == ContentType.JSON) {
+ try {
+ // EapAka token challenge for initial AuthN
+ Log.d(TAG, "initial AuthN");
+ String akaChallengeResponse =
+ new EapAkaResponse(
+ new JSONObject(response.body()).getString(EAP_CHALLENGE_RESPONSE))
+ .getEapAkaChallengeResponse(context, simSubscriptionId);
+ JSONObject postData = new JSONObject();
+ postData.put(EAP_CHALLENGE_RESPONSE, akaChallengeResponse);
+ return challengeResponse(postData, serverUrl);
+ } catch (JSONException jsonException) {
+ Log.e(TAG, "queryEntitlementStatus failed. jsonException: " + jsonException);
+ return null;
+ }
+ } else if (response.contentType() == ContentType.XML) {
+ // Result of fast AuthN
+ Log.d(TAG, "fast AuthN");
+ return response.body();
+ }
+ throw new ServiceEntitlementException("Unexpected http ContentType");
+ }
+
+ private String challengeResponse(JSONObject postData, String serverUrl)
+ throws ServiceEntitlementException {
+ Log.d(TAG, "challengeResponse");
+ HttpRequest request =
+ HttpRequest.builder()
+ .setUrl(serverUrl)
+ .setRequestMethod(RequestMethod.POST)
+ .setPostData(postData)
+ .addRequestProperty(
+ HttpHeaders.ACCEPT,
+ "application/vnd.gsma.eap-relay.v1.0+json, text/vnd.wap"
+ + ".connectivity-xml")
+ .addRequestProperty(HttpHeaders.CONTENT_TYPE,
+ "application/vnd.gsma.eap-relay.v1.0+json")
+ .build();
+
+ HttpResponse response = httpClient.request(request);
+ if (response == null || response.contentType() != ContentType.XML) {
+ throw new ServiceEntitlementException("Unexpected http response.");
+ }
+
+ return response.body();
+ }
+
+ private String entitlementStatusUrl(
+ String appId, String serverUrl, ServiceEntitlementRequest request) {
+ TelephonyManager telephonyManager =
+ context.getSystemService(TelephonyManager.class).createForSubscriptionId(
+ simSubscriptionId);
+ Uri.Builder urlBuilder = Uri.parse(serverUrl).buildUpon();
+ if (TextUtils.isEmpty(request.authenticationToken())) {
+ // EAP_ID required for initial AuthN
+ urlBuilder.appendQueryParameter(
+ EAP_ID,
+ getImsiEap(telephonyManager.getSimOperator(),
+ telephonyManager.getSubscriberId()));
+ } else {
+ // IMSI and token required for fast AuthN.
+ urlBuilder
+ .appendQueryParameter(IMSI, telephonyManager.getSubscriberId())
+ .appendQueryParameter(TOKEN, request.authenticationToken());
+ }
+
+ if (!TextUtils.isEmpty(request.notificationToken())) {
+ urlBuilder
+ .appendQueryParameter(NOTIF_ACTION,
+ Integer.toString(request.notificationAction()))
+ .appendQueryParameter(NOTIF_TOKEN, request.notificationToken());
+ }
+
+ // Assign terminal ID with device IMEI if not set.
+ if (TextUtils.isEmpty(request.terminalId())) {
+ urlBuilder.appendQueryParameter(TERMINAL_ID, telephonyManager.getImei());
+ } else {
+ urlBuilder.appendQueryParameter(TERMINAL_ID, request.terminalId());
+ }
+
+ // Optional query parameters, append them if not empty
+ if (!TextUtils.isEmpty(request.appVersion())) {
+ urlBuilder.appendQueryParameter(APP_VERSION, request.appVersion());
+ }
+
+ if (!TextUtils.isEmpty(request.appName())) {
+ urlBuilder.appendQueryParameter(APP_NAME, request.appName());
+ }
+
+ return urlBuilder
+ // Identity and Authentication parameters
+ .appendQueryParameter(TERMINAL_VENDOR, request.terminalVendor())
+ .appendQueryParameter(TERMINAL_MODEL, request.terminalModel())
+ .appendQueryParameter(TERMIAL_SW_VERSION, request.terminalSoftwareVersion())
+ // General Service parameters
+ .appendQueryParameter(APP, appId)
+ .appendQueryParameter(VERS, Integer.toString(request.configurationVersion()))
+ .appendQueryParameter(ENTITLEMENT_VERSION, request.entitlementVersion())
+ .toString();
+ }
+
+ /**
+ * 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:
+ *
+ * <p>{@code 0<IMSI>@nai.epc.mnc<MNC>.mcc<MCC>.3gppnetwork.org}
+ */
+ @Nullable
+ static String getImsiEap(@Nullable String mccmnc, @Nullable String imsi) {
+ if (mccmnc == null || mccmnc.length() < 5 || imsi == null) {
+ return null;
+ }
+
+ String mcc = mccmnc.substring(0, 3);
+ String mnc = mccmnc.substring(3);
+ if (mnc.length() == 2) {
+ mnc = "0" + mnc;
+ }
+ return "0" + imsi + "@nai.epc.mnc" + mnc + ".mcc" + mcc + ".3gppnetwork.org";
+ }
+}
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java b/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java
new file mode 100644
index 0000000..5021317
--- /dev/null
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaResponse.java
@@ -0,0 +1,370 @@
+/*
+ * 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 android.content.Context;
+import android.telephony.TelephonyManager;
+import android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.libraries.entitlement.ServiceEntitlementException;
+import com.android.libraries.entitlement.eapaka.utils.BytesConverter;
+
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+
+import javax.crypto.Mac;
+import javax.crypto.spec.SecretKeySpec;
+
+import androidx.annotation.Nullable;
+
+/** Generate the response of EAP-AKA token challenge for initial AUTN. */
+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 ATTRIBUTE_RES = 0x03;
+ private static final byte ATTRIBUTE_MAC = 0x0B;
+ private static final String ALGORITHM_HMAC_SHA1 = "HmacSHA1";
+ private static final int RAND_LENGTH = 20;
+ private static final int AUTN_LENGTH = 20;
+ private static final int SHA1_OUTPUT_LENGTH = 20;
+
+ /** RAND length 16. */
+ private static final byte RAND_LEN = 0x10;
+ /** AUTN length 16. */
+ private static final byte AUTN_LEN = 0x10;
+
+ /* 1 for Request, 2 for Response*/
+ private byte code = -1;
+ /* The identifier of Response must same as Request */
+ private byte identifier = -1;
+ /* The total length of full EAP-AKA message, include code, identifier, ... */
+ private int length = -1;
+ /* In EAP-AKA, the Type field is set to 23 */
+ private byte type = -1;
+ /* SubType for AKA-Challenge should be 1 */
+ private byte subType = -1;
+ /* The value of AT_AUTN, network authentication token */
+ private byte[] autn;
+ /* The value of AT_RAND, RAND random number*/
+ private byte[] rand;
+
+ private boolean valid;
+
+ public EapAkaResponse(String eapAkaChallenge) {
+ try {
+ parseEapAkaChallengeRequest(eapAkaChallenge);
+ } catch (Exception e) {
+ Log.e(TAG, "parseEapAkaChallengeRequest Exception:", e);
+ valid = false;
+ }
+ }
+
+ /** Refer to RFC 4187 Section 8.1 Message Format/RFC 3748 Session 4 EAP Packet Format. */
+ private void parseEapAkaChallengeRequest(String request) {
+ if (TextUtils.isEmpty(request)) {
+ return;
+ }
+
+ try {
+ byte[] data = Base64.decode(request, Base64.DEFAULT);
+ if (parseEapAkaHeader(data) && parseRandAndAutn(data)) {
+ valid = 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;
+ }
+ code = data[0];
+ identifier = data[1];
+ length = ((data[2] & 0xff) << 8) | (data[3] & 0xff);
+ type = data[4];
+ subType = data[5];
+
+ // valid 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;
+ }
+
+ /**
+ * 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.
+ */
+ 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 != RAND_LENGTH) {
+ Log.d(TAG, "AT_RAND length is " + length);
+ return false;
+ }
+ rand = new byte[16];
+ System.arraycopy(data, index + 4, rand, 0, 16);
+ } else if (attributeType == ATTRIBUTE_AUTN) {
+ if (length != AUTN_LENGTH) {
+ Log.d(TAG, "AT_AUTN length is " + length);
+ return false;
+ }
+ autn = new byte[16];
+ System.arraycopy(data, index + 4, autn, 0, 16);
+ }
+
+ index += length;
+ } // while
+
+ // check has AT_RAND and AT_AUTH
+ if (rand == null || autn == null) {
+ Log.d(TAG, "Invalid Type Datas!");
+ return false;
+ }
+
+ return true;
+ }
+
+ /**
+ * 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)
+ throws ServiceEntitlementException {
+ if (!valid) {
+ throw new ServiceEntitlementException("EAP-AKA Challenge message not valid!");
+ }
+
+ TelephonyManager telephonyManager =
+ 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.getSubscriberId(),
+ telephonyManager.getSimOperator()),
+ securityContext.getIk(),
+ securityContext.getCk());
+ // K_aut is the key used to calculate MAC
+ if (mk.getAut() == null) {
+ throw new ServiceEntitlementException("Can't generate K_Aut!");
+ }
+
+ // generate EAP-AKA Challenge Response message
+ byte[] challengeResponse =
+ generateEapAkaChallengeResponse(securityContext.getRes(), mk.getAut());
+ if (challengeResponse == null) {
+ throw new ServiceEntitlementException(
+ "Failed to generate EAP-AKA Challenge Response data!");
+ }
+
+ return Base64.encodeToString(challengeResponse, Base64.NO_WRAP).trim();
+ }
+
+ /** Returns Base64 encoded GSM/3G security context for SIM Authentication request. */
+ @Nullable
+ private String getSimAuthChallengeData() {
+ if (!valid) {
+ return null;
+ }
+
+ byte[] challengeData = new byte[RAND_LEN + AUTN_LEN + 2];
+ challengeData[0] = RAND_LEN;
+ System.arraycopy(rand, 0, challengeData, 1, RAND_LEN);
+ challengeData[RAND_LEN + 1] = AUTN_LEN;
+ System.arraycopy(autn, 0, challengeData, RAND_LEN + 2, AUTN_LEN);
+
+ return Base64.encodeToString(challengeData, Base64.NO_WRAP).trim();
+ }
+
+ /** Returns EAP-AKA Challenge response message byte array data or null if failed to generate. */
+ @Nullable
+ public byte[] generateEapAkaChallengeResponse(@Nullable byte[] res, byte[] aut) {
+ if (res == null || aut == null) {
+ return null;
+ }
+
+ byte[] message = createEapAkaChallengeResponse(res);
+
+ // use K_aut as key to calculate mac
+ byte[] mac = calculateMac(aut, message);
+ if (mac == null) {
+ return null;
+ }
+
+ // fill MAC value to the message
+ // The value start index is 8 + AT_RES (4 + res.length) + header of AT_MAC (4)
+ int index = 8 + 4 + res.length + 4;
+ System.arraycopy(mac, 0, message, index, mac.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) {
+ // 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] = 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
+ message[6] = 0x00;
+ message[7] = 0x00;
+
+ int index = 8;
+
+ // 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.
+ 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
+ // the exact length of the RES in bits.
+ byte[] resBitLength = BytesConverter.convertIntegerTo4Bytes(res.length * 8);
+ message[index++] = resBitLength[2];
+ message[index++] = resBitLength[3];
+ System.arraycopy(res, 0, message, index, res.length);
+ index += res.length;
+
+ // set up AT_MAC, RFC 4187, 10.15 AT_MAC
+ message[index++] = ATTRIBUTE_MAC;
+ // fixed length, 5*4 = 20
+ message[index++] = 0x05;
+ // With two bytes reserved
+ message[index++] = 0x00;
+ message[index++] = 0x00;
+
+ // 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;
+ }
+
+ return message;
+ }
+
+ // See RFC 4187, 10.15 AT_MAC, snippet as below, the key must be k_aut
+ //
+ // The MAC algorithm is HMAC-SHA1-128 [RFC2104] keyed hash value. (The
+ // HMAC-SHA1-128 value is obtained from the 20-byte HMAC-SHA1 value by
+ // truncating the output to 16 bytes. Hence, the length of the MAC is
+ // 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) {
+ try {
+ Mac mac = Mac.getInstance(ALGORITHM_HMAC_SHA1);
+ SecretKeySpec secret = new SecretKeySpec(key, ALGORITHM_HMAC_SHA1);
+ mac.init(secret);
+ byte[] output = mac.doFinal(message);
+
+ if (output == null || output.length != SHA1_OUTPUT_LENGTH) {
+ Log.e(TAG, "Invalid result! length should be 20, but " + output.length);
+ return null;
+ }
+
+ byte[] macValue = new byte[16];
+ System.arraycopy(output, 0, macValue, 0, 16);
+ return macValue;
+ } catch (NoSuchAlgorithmException | InvalidKeyException e) {
+ Log.e(TAG, "calculateMac failed!", e);
+ }
+
+ return null;
+ }
+}
diff --git a/java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java b/java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java
new file mode 100644
index 0000000..bb451fd
--- /dev/null
+++ b/java/com/android/libraries/entitlement/eapaka/EapAkaSecurityContext.java
@@ -0,0 +1,151 @@
+/*
+ * 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 android.text.TextUtils;
+import android.util.Base64;
+import android.util.Log;
+
+import com.android.libraries.entitlement.ServiceEntitlementException;
+
+/**
+ * Provide format to handle request/response SIM Authentication with GSM/3G security context.
+ *
+ * <p>Reference ETSI TS 131 102, Section 7.1.2.1 GSM/3G security context.
+ */
+class EapAkaSecurityContext {
+ private static final String TAG = "ServiceEntitlement";
+
+ private static final byte RESPONSE_TAG_SUCCESS = (byte) 0xDB;
+
+ private boolean valid;
+
+ /* Authentication result from SIM */
+ private byte[] res;
+ /* Cipher Key */
+ private byte[] ck;
+ /* Integrity Key */
+ private byte[] ik;
+
+ private EapAkaSecurityContext() {
+ }
+
+ /** Provide {@link EapAkaSecurityContext} from response data. */
+ public static EapAkaSecurityContext from(String response)
+ throws ServiceEntitlementException {
+ EapAkaSecurityContext securityContext = new EapAkaSecurityContext();
+ securityContext.parseResponseData(response);
+ if (!securityContext.isValid()) {
+ throw new ServiceEntitlementException("Invalid SIM EAP-AKA authentication response!");
+ }
+ return securityContext;
+ }
+
+ /**
+ * Parses SIM EAP-AKA Authentication responsed data and returns valid {@link
+ * EapAkaSecurityContext}
+ * for successful data; otherwise, returns invalid.
+ */
+ void parseResponseData(String response) {
+ if (TextUtils.isEmpty(response)) {
+ Log.d(TAG, "parseResponseData but input empty data!");
+ return;
+ }
+
+ 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;
+ }
+
+ // Parse RES
+ index++; // move to RES length byte
+ res = parseTag(index, data);
+ if (res == null) {
+ Log.d(TAG, "Invalid data! can't parse RES!");
+ return;
+ }
+ // Parse CK
+ index += res.length + 1; // move to CK length byte
+ ck = parseTag(index, data);
+ if (ck == null) {
+ Log.d(TAG, "Invalid data! can't parse CK!");
+ return;
+ }
+ // Parse IK
+ index += ck.length + 1; // move to IK length byte
+ ik = parseTag(index, data);
+ if (ik == null) {
+ Log.d(TAG, "Invalid data! can't parse IK!");
+ return;
+ }
+
+ valid = true;
+ } catch (IllegalArgumentException illegalArgumentException) {
+ Log.e(TAG, "Invalid base-64 content");
+ }
+ }
+
+
+ private byte[] parseTag(int index, byte[] src) {
+ // index at the length byte
+ if (index >= src.length) {
+ Log.d(TAG, "No length byte!");
+ return null;
+ }
+ int length = src[index] & 0xff;
+ if (index + length >= src.length) {
+ Log.d(TAG, "Invalid data length!");
+ return null;
+ }
+ index++; // move to first byte of tag value
+ byte[] dest = new byte[length];
+ System.arraycopy(src, index, dest, 0, length);
+
+ return dest;
+ }
+
+ /** Returns {@code valid}. */
+ boolean isValid() {
+ return valid;
+ }
+
+ /** Returns {@code res}. */
+ public byte[] getRes() {
+ return res;
+ }
+
+ /** Returns {@code ck}. */
+ public byte[] getCk() {
+ return ck;
+ }
+
+ /** Returns {@code ik}. */
+ public byte[] getIk() {
+ return ik;
+ }
+}
diff --git a/java/com/android/libraries/entitlement/eapaka/MasterKey.java b/java/com/android/libraries/entitlement/eapaka/MasterKey.java
new file mode 100644
index 0000000..652fa65
--- /dev/null
+++ b/java/com/android/libraries/entitlement/eapaka/MasterKey.java
@@ -0,0 +1,391 @@
+/*
+ * 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 java.nio.charset.StandardCharsets.UTF_8;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.libraries.entitlement.ServiceEntitlementException;
+import com.android.libraries.entitlement.eapaka.utils.BytesConverter;
+
+import java.math.BigInteger;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+
+import androidx.annotation.Nullable;
+
+/**
+ * The class for Master Key.
+ *
+ * <p>Reference : RFC 4187, Section 7. Key Generation MK = SHA1(Identity|IK|CK)
+ */
+class MasterKey {
+ private static final String TAG = "ServiceEntitlement";
+ /* K_encr (128 bits) */
+ private static final int LENGTH_K_ENCR = 16;
+ /* K_aut (128 bits) */
+ private static final int LENGTH_K_AUT = 16;
+ /* Master Session Key (64 bytes) */
+ private static final int LENGTH_MSK = 64;
+ /* Extended Master Session Key (64 bytes) */
+ private static final int LENGTH_EMSK = 64;
+ /* Transient EAP Keys : K_enrc + K_aut + MSK + EMSK */
+ private static final int LENGTH_TEKS = 160;
+
+ /* Master Key */
+ private byte[] masterKey;
+
+ /* Transient EAP Keys */
+ private byte[] encr;
+ private byte[] aut;
+ private byte[] msk;
+ private byte[] emsk;
+
+ private MasterKey() {
+ }
+
+ /** Create the {@code masterKey}. */
+ public static MasterKey create(String identity, @Nullable byte[] ik, @Nullable byte[] ck)
+ throws ServiceEntitlementException {
+ if (TextUtils.isEmpty(identity)
+ || ik == null
+ || ik.length == 0
+ || ck == null
+ || ck.length == 0) {
+ Log.d(TAG, "Can't create master key due to invalid input!");
+ return null;
+ }
+ MasterKey mk = new MasterKey();
+ mk.from(identity, ik, ck);
+ return mk;
+ }
+
+ void from(String identity, byte[] ik, byte[] ck) {
+ // concatenate Identity/IK/CK
+ byte[] identityBytes = identity.getBytes(UTF_8);
+ byte[] data = new byte[identityBytes.length + ik.length + ck.length];
+ int index = 0;
+ System.arraycopy(identityBytes, 0, data, index, identityBytes.length);
+ index += identityBytes.length;
+ System.arraycopy(ik, 0, data, index, ik.length);
+ index += ik.length;
+ System.arraycopy(ck, 0, data, index, ck.length);
+
+ // process SHA1
+ try {
+ MessageDigest messageDigest = MessageDigest.getInstance("SHA-1");
+ messageDigest.update(data);
+ masterKey = messageDigest.digest();
+ } catch (NoSuchAlgorithmException e) {
+ Log.d(TAG, "process sHA-1 failed", e);
+ }
+
+ // Generate TEKs
+ generateTransientEapKeys();
+ }
+
+ /**
+ * Generates TEKs base on RFC 4187, Section 7. Key Generation, snippet as below
+ *
+ * <p>The Master Key is fed into a Pseudo-Random number Function (PRF), which generates
+ * separate
+ * Transient EAP Keys (TEKs) for protecting EAP-AKA packets, as well as a Master Session Key
+ * (MSK)
+ * for link layer security and an Extended Master Session Key (EMSK) for other purposes.
+ */
+ void generateTransientEapKeys() {
+ byte[] teks = generatePsudoRandomNumber();
+
+ if (teks == null || teks.length != 160) {
+ Log.e(TAG, "Invalid TEKs data!");
+ return;
+ }
+
+ int index = 0;
+ encr = new byte[LENGTH_K_ENCR];
+ System.arraycopy(teks, index, encr, 0, LENGTH_K_ENCR);
+ index += LENGTH_K_ENCR;
+ aut = new byte[LENGTH_K_AUT];
+ System.arraycopy(teks, index, aut, 0, LENGTH_K_AUT);
+ index += LENGTH_K_AUT;
+ msk = new byte[LENGTH_MSK];
+ System.arraycopy(teks, index, msk, 0, LENGTH_MSK);
+ index += LENGTH_MSK;
+ emsk = new byte[LENGTH_EMSK];
+ System.arraycopy(teks, index, emsk, 0, LENGTH_EMSK);
+ }
+
+ /** Returns {@code aut}. */
+ public byte[] getAut() {
+ return aut;
+ }
+
+ // RFC 4187 Appendix A. Pseudo-Random Number Generator
+ @Nullable
+ private byte[] generatePsudoRandomNumber() {
+ // Step 1: Choose a new, secret value for the seed-key, XKEY
+ byte[] key = masterKey;
+
+ // 160-bit XKEY and XVAL values are used, so b = 160. On each full
+ // authentication, the Master Key is used as the initial secret seed-key
+ // XKEY.
+ if (key == null || key.length != 20) {
+ Log.e(TAG, "Not a valid XKey!length=" + (key == null ? "null" : key.length));
+ return null;
+ }
+
+ // Step 2: In hexadecimal notation let
+ // t = 67452301 EFCDAB89 98BADCFE 10325476 C3D2E1F0
+ // This is the initial value for H0|H1|H2|H3|H4
+ // in the FIPS SHS [SHA-1]
+ int[] t = {0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476, 0xc3d2e1f0};
+
+ // Step 3: For j = 0 to m - 1 do
+ // 3.1. XSEED_j = 0 /* no optional user input */
+ // 3.2. For i = 0 to 1 do
+ // a. XVAL = (XKEY + XSEED_j) mod 2^b
+ // b. w_i = G(t, XVAL)
+ // c. XKEY = (1 + XKEY + w_i) mod 2^b
+ // 3.3. x_j = w_0|w_1
+ // Step 3: For j = 0 to m - 1 do
+ //
+ // Base on below snippet from RFC 4187, b is 160, x_j is 40 bytes, w_i is 20 bytes, TEKs
+ // length is 160 bytes and m is 160/40=4
+ //
+ // 160-bit XKEY and XVAL values are used, so b = 160. On each full
+ // authentication, the Master Key is used as the initial secret seed-key
+ // XKEY. The optional user input values (XSEED_j) in step 3.1 are set
+ // to zero.
+ // On full authentication, the resulting 320-bit random numbers x_0,
+ // x_1, ..., x_m-1 are concatenated and partitioned into suitable-sized
+ // chunks and used as keys in the following order: K_encr (128 bits),
+ // K_aut (128 bits), Master Session Key (64 bytes), Extended Master
+ // Session Key (64 bytes).
+ byte[] teks = new byte[LENGTH_TEKS];
+ int index = 0;
+ for (int j = 0; j < 4; j++) {
+ // 3.1. XSEED_j = 0, do nothing
+ // 3.2. For i = 0 to 1 do
+ for (int i = 0; i < 2; i++) {
+ // a. XVAL = (XKEY + XSEED_j) mod 2^b
+ byte[] val = key;
+
+ // b. w_i = G(t, XVAL)
+ byte[] w = doFunctionG(t, val);
+ if (w == null || w.length != 20) {
+ Log.e(TAG, "Get invalid w value from G function!");
+ return null;
+ }
+ // fill w to teks
+ System.arraycopy(w, 0, teks, index, 20);
+ index += 20;
+
+ // c. XKEY = (1 + XKEY + w_i) mod 2^b
+ // XKEY is 20 bytes, 160 bits, mod 2^160 is for make sure XKEY just 160 bits
+ int carry = 1;
+ for (int k = 19; k >= 0; k--) {
+ carry += (key[k] & 0xff) + (w[k] & 0xff);
+ key[k] = (byte) (carry & 0xff);
+ // shift one byte and keep carry for next byte calculate
+ carry >>= 8;
+ }
+ }
+ // 3.3. x_j = w_0|w_1, already copy w_0/w_1 to output
+ }
+
+ return teks;
+ }
+
+ // See FIPS 186-2 APPENDIX 3.3. CONSTRUCTING THE FUNCTION G FROM THE SHA-1, snippet as below
+ //
+ // G(t,c) may be constructed using steps (a) - (e) in section 7 of the Specifications for the
+ // Secure Hash Standard. Before executing these steps, {Hj} and M1 must be initialized as
+ // follows:
+ //
+ // i. Initialize the {Hj} by dividing the 160 bit value t into five 32-bit segments as follows:
+ // t = t0 || t1 || t2 || t3 || t4
+ // Then Hj = tj for j = 0 through 4.
+ //
+ // ii. There will be only one message block, M1, which is initialized as follows:
+ // M1 = c || 0^(512-b)
+ // (The first b bits of M1 contain c, and the remaining (512-b) bits are set to zero).
+ //
+ // Then steps (a) through (e) of section 7 are executed, and G(t,c) is the 160 bit string
+ // represented by the five words:
+ // H0 || H1 || H2 || H3 || H4
+ // at the end of step (e).
+ private byte[] doFunctionG(int[] t, byte[] c) {
+ // i. Initialize the {Hj} by dividing the 160 bit value t into five 32-bit segments
+ // 5 segments and every segments is 32 bits/4 bytes
+ byte[][] bytesH = new byte[5][4];
+ for (int i = 0; i < 5; i++) {
+ System.arraycopy(BytesConverter.convertIntegerTo4Bytes(t[i]), 0, bytesH[i], 0, 4);
+ }
+
+ // ii. init message block, M1
+ // The first b bits of M1 contain c, and the remaining (512-b) bits are set to zero
+ byte[] bytesM1 = new byte[64];
+ System.arraycopy(c, 0, bytesM1, 0, 20);
+ for (int i = 20; i < 64; i++) {
+ bytesM1[i] = 0x00;
+ }
+
+ // See FIPS PUB 180-1, Secure Hash Standard
+ // Section 7. COMPUTING THE MESSAGE DIGEST which defined steps (a) - (e)
+
+ // The words of the 80-word sequence are labeled W0, W1,..., W79.
+ byte[][] bytesW = new byte[80][4];
+
+ // a. Divide Mi into 16 words W0, W1, ... , W15, where W0 is the left-most word.
+ for (int i = 0; i < 16; i++) {
+ System.arraycopy(bytesM1, i * 4, bytesW[i], 0, 4);
+ }
+
+ // b. For t = 16 to 79 let Wt = S^1(Wt-3 XOR Wt-8 XOR Wt-14 XOR Wt-16).
+ for (int i = 16; i < 80; i++) {
+ bytesW[i] =
+ doFunctionS(1,
+ doXor(bytesW[i - 3], bytesW[i - 8], bytesW[i - 14], bytesW[i - 16]));
+ }
+
+ // c. Let A = H0, B = H1, C = H2, D = H3, E = H4.
+ byte[] bytesA = new byte[4];
+ byte[] bytesB = new byte[4];
+ byte[] bytesC = new byte[4];
+ byte[] bytesD = new byte[4];
+ byte[] bytesE = new byte[4];
+ System.arraycopy(bytesH[0], 0, bytesA, 0, 4);
+ System.arraycopy(bytesH[1], 0, bytesB, 0, 4);
+ System.arraycopy(bytesH[2], 0, bytesC, 0, 4);
+ System.arraycopy(bytesH[3], 0, bytesD, 0, 4);
+ System.arraycopy(bytesH[4], 0, bytesE, 0, 4);
+
+ // d. For t = 0 to 79 do
+ // TEMP = S^5(A) + ft(B,C,D) + E + Wt + Kt;
+ // E = D; D = C; C = S^30(B); B = A; A = TEMP;
+ for (int i = 0; i < 80; i++) {
+ int tmpA = new BigInteger(doFunctionS(5, bytesA)).intValue();
+ int tmpF = doFunctionF(i, bytesB, bytesC, bytesD);
+ int tmpE = new BigInteger(bytesE).intValue();
+ int tmpW = new BigInteger(bytesW[i]).intValue();
+ int tmpK = doFunctionK(i);
+ int temp = tmpA + tmpF + tmpE + tmpW + tmpK;
+ bytesE = bytesD;
+ bytesD = bytesC;
+ bytesC = doFunctionS(30, bytesB);
+ bytesB = bytesA;
+ bytesA = BytesConverter.convertIntegerTo4Bytes(temp);
+ }
+
+ // e. Let H0 = H0 + A, H1 = H1 + B, H2 = H2 + C, H3 = H3 + D, H4 = H4 + E.
+ bytesH[0] = addTwoBytes(bytesH[0], bytesA);
+ bytesH[1] = addTwoBytes(bytesH[1], bytesB);
+ bytesH[2] = addTwoBytes(bytesH[2], bytesC);
+ bytesH[3] = addTwoBytes(bytesH[3], bytesD);
+ bytesH[4] = addTwoBytes(bytesH[4], bytesE);
+
+ // After processing Mn, the message digest is the 160-bit string represented by the 5 words
+ // H0 H1 H2 H3 H4.
+ byte[] output = new byte[20];
+ System.arraycopy(bytesH[0], 0, output, 0, 4);
+ System.arraycopy(bytesH[1], 0, output, 4, 4);
+ System.arraycopy(bytesH[2], 0, output, 8, 4);
+ System.arraycopy(bytesH[3], 0, output, 12, 4);
+ System.arraycopy(bytesH[4], 0, output, 16, 4);
+
+ return output;
+ }
+
+ private static byte[] addTwoBytes(byte[] a, byte[] b) {
+ BigInteger iA = new BigInteger(a);
+ BigInteger iB = new BigInteger(b);
+ return BytesConverter.convertIntegerTo4Bytes(iA.add(iB).intValue());
+ }
+
+ // See FIPS PUB 180-1, Section 3. OPERATIONS ON WORDS
+ // Sn(X) = (X << n) OR (X >> 32-n).
+ private static byte[] doFunctionS(int n, byte[] dataX) {
+ BigInteger leftShiftValue = new BigInteger(dataX).shiftLeft(n);
+
+ // BigInteger.shiftRight would fill 1 if the left-most bit is 1, so use '>>>'
+ int value = new BigInteger(dataX).intValue();
+ value = value >>> (32 - n); // X should be 32 bits
+ BigInteger rightShiftValue = BigInteger.valueOf(value);
+ BigInteger result = leftShiftValue.or(rightShiftValue);
+ return BytesConverter.convertIntegerTo4Bytes(result.intValue());
+ }
+
+ private static byte[] doXor(byte[] a, byte[] b, byte[] c, byte[] d) {
+ BigInteger iA = new BigInteger(a);
+ BigInteger iB = new BigInteger(b);
+ BigInteger iC = new BigInteger(c);
+ BigInteger iD = new BigInteger(d);
+ BigInteger result = iA.xor(iB).xor(iC).xor(iD);
+ return BytesConverter.convertIntegerTo4Bytes(result.intValue());
+ }
+
+ // See FIPS PUB 180-1, Section 5. FUNCTIONS USED
+ // A sequence of logical functions f0, f1,..., f79 is used in the SHA-1. Each ft, 0 <= t <= 79,
+ // operates on three 32-bit words B, C, D and produces a 32-bit word as output. ft(B,C,D) is
+ // defined as follows: for words B, C, D,
+ //
+ // ft(B,C,D) = (B AND C) OR ((NOT B) AND D) (0 <= t <= 19)
+ // ft(B,C,D) = B XOR C XOR D (20 <= t <= 39)
+ // ft(B,C,D) = (B AND C) OR (B AND D) OR (C AND D) (40 <= t <= 59)
+ // ft(B,C,D) = B XOR C XOR D (60 <= t <= 79).
+ private static int doFunctionF(int t, byte[] b, byte[] c, byte[] d) {
+ BigInteger iB = new BigInteger(b);
+ BigInteger iC = new BigInteger(c);
+ BigInteger iD = new BigInteger(d);
+ BigInteger result = BigInteger.valueOf(-1);
+ if (0 <= t && t <= 19) {
+ result = iB.and(iC).or(iB.not().and(iD));
+ } else if (20 <= t && t <= 39) {
+ result = iB.xor(iC).xor(iD);
+ } else if (40 <= t && t <= 59) {
+ result = iB.and(iC).or(iB.and(iD)).or(iC.and(iD));
+ } else if (60 <= t && t <= 79) {
+ result = iB.xor(iC).xor(iD);
+ }
+
+ return result.intValue();
+ }
+
+ // See FIPS PUB 180-1, Section 6. CONSTANTS USED
+ //
+ // A sequence of constant words K(0), K(1), ... , K(79) is used in the SHA-1. In hex these are
+ // given by
+ // K = 5A827999 ( 0 <= t <= 19)
+ // Kt = 6ED9EBA1 (20 <= t <= 39)
+ // Kt = 8F1BBCDC (40 <= t <= 59)
+ // Kt = CA62C1D6 (60 <= t <= 79).
+ private static int doFunctionK(int t) {
+ if (0 <= t && t <= 19) {
+ return 0x5A827999;
+ } else if (20 <= t && t <= 39) {
+ return 0x6ED9EBA1;
+ } else if (40 <= t && t <= 59) {
+ return 0x8F1BBCDC;
+ } else if (60 <= t && t <= 79) {
+ return 0xCA62C1D6;
+ }
+
+ return -1;
+ }
+}
diff --git a/java/com/android/libraries/entitlement/http/HttpClient.java b/java/com/android/libraries/entitlement/http/HttpClient.java
new file mode 100644
index 0000000..79f42ff
--- /dev/null
+++ b/java/com/android/libraries/entitlement/http/HttpClient.java
@@ -0,0 +1,170 @@
+/*
+ * 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.android.libraries.entitlement.http.HttpConstants.RequestMethod.POST;
+
+import static com.google.common.base.Strings.nullToEmpty;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.util.concurrent.TimeUnit.SECONDS;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.libraries.entitlement.ServiceEntitlementException;
+import com.android.libraries.entitlement.http.HttpConstants.ContentType;
+import com.android.libraries.entitlement.utils.StreamUtils;
+
+import java.io.DataOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.net.HttpURLConnection;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.Map;
+
+import androidx.annotation.WorkerThread;
+
+/** Implement the HTTP request method according to TS.43 specification. */
+public class HttpClient {
+ private static final String TAG = "ServiceEntitlement";
+ private static final boolean DEBUG = false; // STOPSHIP if true
+
+ private static final int SOCKET_TIMEOUT_VALUE = (int) SECONDS.toMillis(30);
+ private static final int CONNECT_TIMEOUT_VALUE = (int) SECONDS.toMillis(30);
+
+ private HttpURLConnection connection;
+
+ @WorkerThread
+ // TODO(b/177544547): Add debug messages
+ public HttpResponse request(HttpRequest request) throws ServiceEntitlementException {
+ try {
+ logd("HttpClient.request url: " + request.url());
+ createConnection(request);
+ if (connection == null) {
+ logd("HttpClient.request connection is null");
+ throw new ServiceEntitlementException("No connection");
+ }
+ logd("HttpClient.request headers (partial): " + connection.getRequestProperties());
+ if (POST.equals(request.requestMethod())) {
+ try (OutputStream out = new DataOutputStream(connection.getOutputStream())) {
+ out.write(request.postData().toString().getBytes(UTF_8));
+ logd("HttpClient.request post data: " + request.postData());
+ }
+ }
+ connection.connect(); // This is to trigger SocketTimeoutException early
+ HttpResponse response = getHttpResponse(connection);
+ Log.d(TAG, "HttpClient.response : " + response);
+ return response;
+ } catch (IOException e) {
+ InputStream errorStream = connection.getErrorStream();
+ Log.e(
+ TAG,
+ "HttpClient.request() error: " + StreamUtils.inputStreamToStringSafe(
+ errorStream));
+ throw new ServiceEntitlementException("request failed! exception: " + e.getMessage());
+ } finally {
+ closeConnection();
+ }
+ }
+
+ private void createConnection(HttpRequest request) throws ServiceEntitlementException {
+ try {
+ URL url = new URL(request.url());
+ connection = (HttpURLConnection) url.openConnection();
+
+ // add HTTP headers
+ for (Map.Entry<String, String> entry : request.requestProperties().entrySet()) {
+ connection.addRequestProperty(entry.getKey(), entry.getValue());
+ }
+
+ // set parameters
+ connection.setRequestMethod(request.requestMethod());
+ connection.setConnectTimeout(CONNECT_TIMEOUT_VALUE);
+ connection.setReadTimeout(SOCKET_TIMEOUT_VALUE);
+ if (POST.equals(request.requestMethod())) {
+ connection.setDoOutput(true);
+ }
+ } catch (IOException e) {
+ Log.e(TAG, "IOException: " + e.getMessage());
+ throw new ServiceEntitlementException("Configure connection failed!" + e.getMessage());
+ }
+ }
+
+ private void closeConnection() {
+ if (connection != null) {
+ connection.disconnect();
+ connection = null;
+ }
+ }
+
+ private static HttpResponse getHttpResponse(HttpURLConnection connection)
+ throws ServiceEntitlementException {
+ try {
+ int responseCode = connection.getResponseCode();
+ logd("HttpClient.response headers: " + connection.getHeaderFields());
+ if (responseCode != HttpURLConnection.HTTP_OK) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS, responseCode, null,
+ "Invalid connection response", null);
+ }
+ String responseBody = readResponse(connection);
+ logd("HttpClient.response body: " + responseBody);
+ return HttpResponse.builder()
+ .setContentType(getContentType(connection))
+ .setBody(responseBody)
+ .setResponseCode(responseCode)
+ .setResponseMessage(nullToEmpty(connection.getResponseMessage()))
+ .build();
+ } catch (IOException e) {
+ throw new ServiceEntitlementException(
+ ServiceEntitlementException.ERROR_HTTP_STATUS_NOT_SUCCESS, 0, null,
+ "Read response failed!", e);
+ }
+ }
+
+ private static String readResponse(URLConnection connection) throws IOException {
+ try (InputStream in = connection.getInputStream()) {
+ return StreamUtils.inputStreamToStringSafe(in);
+ }
+ }
+
+ private static int getContentType(URLConnection connection) {
+ String contentType = connection.getHeaderField(ContentType.NAME);
+ if (TextUtils.isEmpty(contentType)) {
+ return ContentType.UNKNOWN;
+ }
+
+ if (contentType.contains("xml")) {
+ return ContentType.XML;
+ } else if ("text/vnd.wap.connectivity".equals(contentType)) {
+ // Workaround that a server vendor uses this type for XML
+ return ContentType.XML;
+ } else if (contentType.contains("json")) {
+ return ContentType.JSON;
+ }
+ return ContentType.UNKNOWN;
+ }
+
+ private static void logd(String message) {
+ if (DEBUG) {
+ Log.d(TAG, message);
+ }
+ }
+}
diff --git a/java/com/android/libraries/entitlement/http/HttpConstants.java b/java/com/android/libraries/entitlement/http/HttpConstants.java
new file mode 100644
index 0000000..c4ed5e2
--- /dev/null
+++ b/java/com/android/libraries/entitlement/http/HttpConstants.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+/** Http constants using for entitlement flow of TS.43. */
+public final class HttpConstants {
+ private HttpConstants() {}
+
+ /** Possible request methods for Entitlement server response. */
+ public static final class RequestMethod {
+ private RequestMethod() {}
+
+ public static final String GET = "GET";
+ public static final String POST = "POST";
+ }
+
+ /** Possible content type for Entitlement server response. */
+ public static final class ContentType {
+ private ContentType() {}
+
+ public static final int UNKNOWN = -1;
+ public static final int JSON = 0;
+ public static final int XML = 1;
+
+ public static final String NAME = "Content-Type";
+ }
+}
diff --git a/java/com/android/libraries/entitlement/http/HttpRequest.java b/java/com/android/libraries/entitlement/http/HttpRequest.java
new file mode 100644
index 0000000..af81b10
--- /dev/null
+++ b/java/com/android/libraries/entitlement/http/HttpRequest.java
@@ -0,0 +1,80 @@
+/*
+ * 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 android.util.ArrayMap;
+
+import com.google.auto.value.AutoValue;
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+import org.json.JSONObject;
+
+/** The parameters of the http request. */
+@AutoValue
+public abstract class HttpRequest {
+
+ public abstract String url();
+
+ public abstract String requestMethod();
+
+ public abstract JSONObject postData();
+
+ public abstract ImmutableMap<String, String> requestValues();
+
+ public abstract ImmutableMap<String, String> requestProperties();
+
+ /** Builder of {@link HttpRequest}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ private final Map<String, String> values = new ArrayMap<>();
+ private final Map<String, String> properties = new ArrayMap<>();
+
+ public abstract HttpRequest build();
+
+ public abstract Builder setUrl(String url);
+
+ public abstract Builder setRequestMethod(String requestMethod);
+
+ public abstract Builder setPostData(JSONObject postData);
+
+ abstract Builder setRequestValues(ImmutableMap<String, String> value);
+
+ abstract Builder setRequestProperties(ImmutableMap<String, String> properties);
+
+ public Builder addRequestValues(String key, String value) {
+ values.put(key, value);
+ return this.setRequestValues(ImmutableMap.copyOf(values));
+ }
+
+ public Builder addRequestProperty(String key, String value) {
+ properties.put(key, value);
+ return this.setRequestProperties(ImmutableMap.copyOf(properties));
+ }
+ }
+
+ public static Builder builder() {
+ return new AutoValue_HttpRequest.Builder()
+ .setUrl("")
+ .setRequestMethod("")
+ .setPostData(new JSONObject())
+ .setRequestValues(ImmutableMap.of())
+ .setRequestProperties(ImmutableMap.of());
+ }
+}
diff --git a/java/com/android/libraries/entitlement/http/HttpResponse.java b/java/com/android/libraries/entitlement/http/HttpResponse.java
new file mode 100644
index 0000000..1cb165e
--- /dev/null
+++ b/java/com/android/libraries/entitlement/http/HttpResponse.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.android.libraries.entitlement.http.HttpConstants.ContentType;
+
+import com.google.auto.value.AutoValue;
+
+/** The response of the http request. */
+@AutoValue
+public abstract class HttpResponse {
+
+ /** Content type of the response. */
+ public abstract int contentType();
+
+ public abstract String body();
+
+ public abstract int responseCode();
+
+ public abstract String responseMessage();
+
+ /** Builder of {@link HttpResponse}. */
+ @AutoValue.Builder
+ public abstract static class Builder {
+
+ public abstract HttpResponse build();
+
+ public abstract Builder setContentType(int contentType);
+
+ public abstract Builder setBody(String body);
+
+ public abstract Builder setResponseCode(int responseCode);
+
+ public abstract Builder setResponseMessage(String responseMessage);
+ }
+
+ public static Builder builder() {
+ return new AutoValue_HttpResponse.Builder()
+ .setContentType(ContentType.UNKNOWN)
+ .setBody("")
+ .setResponseCode(0)
+ .setResponseMessage("");
+ }
+
+ @Override
+ public final String toString() {
+ return new StringBuilder("HttpResponse{")
+ .append("contentType=")
+ .append(contentType())
+ .append(" body=(")
+ .append(body().length())
+ .append(" characters)")
+ .append(" responseCode=")
+ .append(responseCode())
+ .append(" responseMessage=")
+ .append(responseMessage())
+ .append("}")
+ .toString();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/utils/BytesConverter.java b/java/com/android/libraries/entitlement/utils/BytesConverter.java
new file mode 100644
index 0000000..034ac9c
--- /dev/null
+++ b/java/com/android/libraries/entitlement/utils/BytesConverter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.utils;
+
+import java.nio.ByteBuffer;
+
+import androidx.annotation.Nullable;
+
+public class BytesConverter {
+ private static final int INTEGER_SIZE = 4; // 4 bytes
+
+ // A table mapping from a number to a hex character for fast encoding hex strings.
+ private static final char[] HEX_CHARS = {
+ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'
+ };
+
+ /**
+ * Converts a byte array into a String of hexadecimal characters.
+ *
+ * @param bytes an array of bytes
+ * @return hex string representation of bytes array
+ */
+ @Nullable
+ public static String convertBytesToHexString(byte[] bytes) {
+ if (bytes == null) {
+ return null;
+ }
+
+ StringBuilder ret = new StringBuilder(2 * bytes.length);
+
+ for (int i = 0; i < bytes.length; i++) {
+ int b;
+ b = 0x0f & (bytes[i] >> 4);
+ ret.append(HEX_CHARS[b]);
+ b = 0x0f & bytes[i];
+ ret.append(HEX_CHARS[b]);
+ }
+
+ return ret.toString();
+ }
+
+ /** Converts integer to 4 bytes. */
+ public static byte[] convertIntegerTo4Bytes(int value) {
+ return ByteBuffer.allocate(INTEGER_SIZE).putInt(value).array();
+ }
+}
diff --git a/java/com/android/libraries/entitlement/utils/StreamUtils.java b/java/com/android/libraries/entitlement/utils/StreamUtils.java
new file mode 100644
index 0000000..1ff2ffc
--- /dev/null
+++ b/java/com/android/libraries/entitlement/utils/StreamUtils.java
@@ -0,0 +1,54 @@
+/*
+ * 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.utils;
+
+import java.io.BufferedInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+
+/** Utility methods about InputStream. */
+public final class StreamUtils {
+
+ private static final int BUFFER_SIZE = 1024;
+
+ private StreamUtils() {
+ }
+
+ /** Reads an {@link InputStream} into a string. */
+ public static String inputStreamToString(InputStream inputStream) throws IOException {
+ try (BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);
+ ByteArrayOutputStream result = new ByteArrayOutputStream()) {
+ byte[] buffer = new byte[BUFFER_SIZE];
+ int length = 0;
+ while ((length = inputStream.read(buffer)) != -1) {
+ result.write(buffer, 0, length);
+ }
+ return result.toString(StandardCharsets.UTF_8.name());
+ }
+ }
+
+ /** Reads an {@link InputStream} into a string. Returns an empty string if any error. */
+ public static String inputStreamToStringSafe(InputStream inputStream) {
+ try {
+ return inputStreamToString(inputStream);
+ } catch (IOException e) {
+ return "";
+ }
+ }
+}
diff --git a/java/com/google/android/libraries/entitlement/CarrierData.java b/java/com/google/android/libraries/entitlement/CarrierData.java
deleted file mode 100644
index 2153fda..0000000
--- a/java/com/google/android/libraries/entitlement/CarrierData.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (C) 2020 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.google.android.libraries.entitlement;
-
-import com.google.auto.value.AutoValue;
-
-/**
- * Carrier specific customization to be used in the service entitlement queries and operations.
- *
- * @see #ServiceEntitlement
- */
-@AutoValue
-public abstract class CarrierData {
- /**
- * The carrier's entitlement server URL. If not set, will use {@code
- * https://aes.mnc<MNC>.mcc<MCC>.pub.3gppnetwork.org} as defined in GSMA spec TS.43 section 2.1.
- */
- public abstract String serverUrl();
-
- // Builder...
-}
diff --git a/java/com/google/android/libraries/entitlement/EsimOdsaOperation.java b/java/com/google/android/libraries/entitlement/EsimOdsaOperation.java
deleted file mode 100644
index 1247824..0000000
--- a/java/com/google/android/libraries/entitlement/EsimOdsaOperation.java
+++ /dev/null
@@ -1,288 +0,0 @@
-/*
- * Copyright (C) 2020 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.google.android.libraries.entitlement;
-
-import com.google.auto.value.AutoValue;
-
-/**
- * HTTP request parameters specific to on device service actiavation (ODSA). See GSMA spec TS.43
- * section 6.2.
- */
-@AutoValue
-public abstract class EsimOdsaOperation {
- /** OSDA operation: CheckEligibility. */
- public static final String OPERATION_CHECK_ELIGIBILITY = "CheckEligibility";
- /** OSDA operation: ManageSubscription. */
- public static final String OPERATION_MANAGE_SUBSCRIPTION = "ManageSubscription";
- /** OSDA operation: ManageService. */
- public static final String OPERATION_MANAGE_SERVICE = "ManageService";
- /** OSDA operation: AcquireConfiguration. */
- public static final String OPERATION_ACQUIRE_CONFIGURATION = "AcquireConfiguration";
-
- /** Indicates that operation_type is not set. */
- static final int OPERATION_TYPE_NOT_SET = -1;
- /** To activate a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
- public static final int OPERATION_TYPE_SUBSCRIBE = 0;
- /** To cancel a subscription, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
- public static final int OPERATION_TYPE_UNSUBSCRIBE = 1;
- /** To manage an existing subscription, for {@link #OPERATION_MANAGE_SUBSCRIPTION}. */
- public static final int OPERATION_TYPE_CHANGE_SUBSCRIPTION = 2;
- /**
- * To transfer a subscription from an existing device, used by {@link
- * #OPERATION_MANAGE_SUBSCRIPTION}.
- */
- public static final int OPERATION_TYPE_TRANSFER_SUBSCRIPTION = 3;
- /**
- * To inform the network of a subscription update, used by {@link #OPERATION_MANAGE_SUBSCRIPTION}.
- */
- public static final int OPERATION_TYPE_UPDATE_SUBSCRIPTION = 4;
- /** To activate a service, used by {@link #OPERATION_MANAGE_SERVICE}. */
- public static final int OPERATION_TYPE_ACTIVATE_SERVICE = 10;
- /** To deactivate a service, used by {@link #OPERATION_MANAGE_SERVICE}. */
- public static final int OPERATION_TYPE_DEACTIVATE_SERVICE = 11;
-
- /** Indicates the companion device carries the same MSISDN as the primary device. */
- public static final String COMPANION_SERVICE_SHAERED_NUMBER = "SharedNumber";
- /** Indicates the companion device carries a different MSISDN as the primary device. */
- public static final String COMPANION_SERVICE_DIFFERENT_NUMBER = "DiffNumber";
-
- /** Returns the eSIM ODSA operation. Used by HTTP parameter "operation". */
- public abstract String operation();
-
- /**
- * Returns the detiled type of the eSIM ODSA operation. Used by HTTP parameter "operation_type".
- */
- public abstract int operationType();
-
- /**
- * Returns the unique identifier of the companion device, like IMEI. Used by HTTP parameter
- * "companion_terminal_id".
- */
- public abstract String companionTerminalId();
-
- /**
- * Returns the OEM of the companion device. Used by HTTP parameter "companion_terminal_vendor".
- */
- public abstract String companionTerminalVendor();
-
- /**
- * Returns the model of the companion device. Used by HTTP parameter "companion_terminal_model".
- */
- public abstract String companionTerminalModel();
-
- /**
- * Returns the software version of the companion device. Used by HTTP parameter
- * "companion_terminal_sw_version".
- */
- public abstract String companionTerminalSoftwareVersion();
-
- /**
- * Returns the user-friendly version of the companion device. Used by HTTP parameter
- * "companion_terminal_friendly_name".
- */
- public abstract String companionTerminalFriendlyName();
-
- /**
- * Returns the service type of the companion device, e.g. if the MSISDN is same as the primary
- * device. Used by HTTP parameter "companion_terminal_service".
- */
- public abstract String companionTerminalService();
-
- /**
- * Returns the ICCID of the companion device. Used by HTTP parameter "companion_terminal_iccid".
- */
- public abstract String companionTerminalIccid();
-
- /**
- * Returns the ICCID of the companion device. Used by HTTP parameter "companion_terminal_iccid".
- */
- public abstract String companionTerminalEid();
-
- /** Returns the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_eid". */
- public abstract String terminalIccid();
-
- /**
- * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
- * "terminal_eid".
- */
- public abstract String terminalEid();
-
- /**
- * Returns the unique identifier of the primary device eSIM, like the IMEI associated with the
- * eSIM. Used by HTTP parameter "target_terminal_id".
- */
- public abstract String targetTerminalId();
-
- /** Returns the ICCID primary device eSIM. Used by HTTP parameter "target_terminal_iccid". */
- public abstract String targetTerminalIccid();
-
- /**
- * Returns the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
- * "target_terminal_eid".
- */
- public abstract String targetTerminalEid();
-
- /** Returns a new {@link Builder} object. */
- public static Builder builder() {
- return new AutoValue_EsimOdsaOperation.Builder().setOperationType(OPERATION_TYPE_NOT_SET);
- }
-
- /**
- * Builder.
- *
- * <p>For ODSA, the rule of which parameters are required varies or each operation/opeation_type.
- * The Javadoc below gives high-level description, but please refer to GMSA spec TS.43 section 6.2
- * for details.
- */
- @AutoValue.Builder
- public abstract static class Builder {
- /**
- * Sets the eSIM ODSA operation. Used by HTTP parameter "operation".
- *
- * <p>Required.
- *
- * @see #OPERATION_CHECK_ELIGIBILITY
- * @see #OPERATION_MANAGE_SUBSCRIPTION
- * @see #OPERATION_MANAGE_SERVICE
- * @see #OPERATION_ACQUIRE_CONFIGURATION
- */
- public abstract Builder setOperation(String value);
-
- /**
- * Sets the detiled type of the eSIM ODSA operation. Used by HTTP parameter "operation_type" if
- * set.
- *
- * <p>Required by some operation.
- *
- * @see #OPERATION_TYPE_SUBSCRIBE
- * @see #OPERATION_TYPE_UNSUBSCRIBE
- * @see #OPERATION_TYPE_CHANGE_SUBSCRIPTION
- * @see #OPERATION_TYPE_TRANSFER_SUBSCRIPTION
- * @see #OPERATION_TYPE_UPDATE_SUBSCRIPTION
- * @see #OPERATION_TYPE_ACTIVATE_SERVICE
- * @see #OPERATION_TYPE_DEACTIVATE_SERVICE
- */
- public abstract Builder setOperationType(int value);
-
- /**
- * Sets the unique identifier of the companion device, like IMEI. Used by HTTP parameter
- * "companion_terminal_id" if set.
- *
- * <p>Used by companion device ODSA operation.
- */
- public abstract Builder setCompanionTerminalId(String value);
-
- /**
- * Sets the OEM of the companion device. Used by HTTP parameter "companion_terminal_vendor" if
- * set.
- *
- * <p>Used by companion device ODSA operation.
- */
- public abstract Builder setCompanionTerminalVendor(String value);
-
- /**
- * Sets the model of the companion device. Used by HTTP parameter "companion_terminal_model" if
- * set.
- *
- * <p>Used by companion device ODSA operation.
- */
- public abstract Builder setCompanionTerminalModel(String value);
-
- /**
- * Sets the software version of the companion device. Used by HTTP parameter
- * "companion_terminal_sw_version" if set.
- *
- * <p>Used by companion device ODSA operation.
- */
- public abstract Builder setCompanionTerminalSoftwareVersion(String value);
-
- /**
- * Sets the user-friendly version of the companion device. Used by HTTP parameter
- * "companion_terminal_friendly_name" if set.
- *
- * <p>Used by companion device ODSA operation.
- */
- public abstract Builder setCompanionTerminalFriendlyName(String value);
-
- /**
- * Sets the service type of the companion device, e.g. if the MSISDN is same as the primary
- * device. Used by HTTP parameter "companion_terminal_service" if set.
- *
- * <p>Used by companion device ODSA operation.
- *
- * @see #COMPANION_SERVICE_SHAERED_NUMBER
- * @see #COMPANION_SERVICE_DIFFERENT_NUMBER
- */
- public abstract Builder setCompanionTerminalService(String value);
-
- /**
- * Sets the ICCID of the companion device. Used by HTTP parameter "companion_terminal_iccid" if
- * set.
- *
- * <p>Used by companion device ODSA operation.
- */
- public abstract Builder setCompanionTerminalIccid(String value);
-
- /**
- * Sets the eUICC identifier (EID) of the companion device. Used by HTTP parameter
- * "companion_terminal_eid" if set.
- *
- * <p>Used by companion device ODSA operation.
- */
- public abstract Builder setCompanionTerminalEid(String value);
-
- /**
- * Sets the ICCID of the primary device eSIM. Used by HTTP parameter "terminal_eid" if set.
- *
- * <p>Used by primary device ODSA operation.
- */
- public abstract Builder setTerminalIccid(String value);
-
- /**
- * Sets the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
- * "terminal_eid" if set.
- *
- * <p>Used by primary device ODSA operation.
- */
- public abstract Builder setTerminalEid(String value);
-
- /**
- * Sets the unique identifier of the primary device eSIM, like the IMEI associated with the
- * eSIM. Used by HTTP parameter "target_terminal_id" if set.
- *
- * <p>Used by primary device ODSA operation.
- */
- public abstract Builder setTargetTerminalId(String value);
-
- /**
- * Sets the ICCID primary device eSIM. Used by HTTP parameter "target_terminal_iccid" if set.
- *
- * <p>Used by primary device ODSA operation.
- */
- public abstract Builder setTargetTerminalIccid(String value);
-
- /**
- * Sets the eUICC identifier (EID) of the primary device eSIM. Used by HTTP parameter
- * "target_terminal_eid" if set.
- *
- * <p>Used by primary device ODSA operation.
- */
- public abstract Builder setTargetTerminalEid(String value);
-
- public abstract EsimOdsaOperation build();
- }
-}
diff --git a/java/com/google/android/libraries/entitlement/ServiceEntitlement.java b/java/com/google/android/libraries/entitlement/ServiceEntitlement.java
deleted file mode 100644
index ea4eec1..0000000
--- a/java/com/google/android/libraries/entitlement/ServiceEntitlement.java
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 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.google.android.libraries.entitlement;
-
-import java.util.List;
-
-/**
- * Implemnets protocol for carrier service entitlement configuration query and operation, based on
- * GSMA TS.43 spec.
- */
-public class ServiceEntitlement {
- /** App ID for Voice-Over-LTE entitlement. */
- public static final String APP_VOLTE = "ap2003";
- /** App ID for Voice-Over-WiFi entitlement. */
- public static final String APP_VOWIFI = "ap2004";
- /** App ID for SMS-Over-IP entitlement. */
- public static final String APP_SMSOIP = "ap2005";
- /** App ID for on device service activation (OSDA) for companion device. */
- public static final String APP_ODSA_COMPANION = "ap2006";
- /** App ID for on device service activation (OSDA) for primary device. */
- public static final String APP_ODSA_PRIMARY = "ap2009";
-
- /**
- * Creates an instance for service entitlement configuration query and operation for the carrier.
- *
- * @param carrierData carrier specific data used in the queries and operations.
- * @param simSubscriptionId the subscroption ID of the carrier's SIM on device. This indicates
- * which SIM to retrieve IMEI/IMSI from and perform EAP-AKA authentication with. See {@link
- * android.telephony.SubscriptionManager} for how to get the subscroption ID.
- */
- public ServiceEntitlement(CarrierData carrierData, int simSubscriptionId) {}
-
- /**
- * Retrieves service entitlement configuration. For on device service activation (ODSA) of eSIM
- * for companion/primary devices, use {@link #performEsimOdsa} instead.
- *
- * <p>Supported {@code appId}: {@link #APP_VOLTE}, {@link #APP_VOWIFI}, {@link #APP_SMSOIP}.
- *
- * <p>This method sends an HTTP GET request to entitlement server, responds to EAP-AKA challenge
- * if needed, and returns the raw configuration doc as a string. The following parameters are set
- * in the HTTP request:
- *
- * <ul>
- * <li>"app": {@code appId}
- * <li>"vers": 0, or {@code request.configurationVersion()} if it's not 0.
- * <li>"entitlement_version": "2.0", or {@code request.entitlementVersion()} if it's not empty.
- * <li>"token": not set, or {@code request.authenticationToken()} if it's not empty.
- * <li>"IMSI": if "token" is set, set to {@link android.telephony.TelephonyManager#getImei}.
- * <li>"EAP_ID": if "token" is not set, set this parameter to trigger embedded EAP-AKA
- * authentication as decribed in TS.43 section 2.6.1. Its value is derived from IMSI as per
- * GSMA spec RCC.14 section C.2.
- * <li>"terminal_id": IMEI, or {@code request.terminalId()} if it's not empty.
- * <li>"terminal_vendor": {@link android.os.Build#MANUFACTURER}, or {@code
- * request.terminalVendor()} if it's not empty.
- * <li>"terminal_model": {@link android.os.Build#MODEL}, or {@code request.terminalModel()} if
- * it's not empty.
- * <li>"terminal_sw_version": {@llink android.os.Build.VERSION#BASE_OS}, or {@code
- * request.terminalSoftwareVersion()} if it's not empty.
- * <li>"app_name": not set, or {@code request.appName()} if it's not empty.
- * <li>"app_version": not set, or {@code request.appVersion()} if it's not empty.
- * <li>"notif_token": not set, or {@code request.notificationToken()} if it's not empty.
- * <li>"notif_action": {@code request.notificationAction()} if "notif_token" is set, otherwise
- * not set.
- * </ul>
- *
- * <p>Requires permission: READ_PRIVILEGED_PHONE_STATE, or carrier privilege.
- *
- * @param appId an app ID string defined in TS.43 section 2.2, e.g. {@link #APP_VOWIFI}.
- * @param request contains parameters that can be used in the HTTP request.
- */
- public String queryEntitlementStatus(String appId, ServiceEntitlementRequest request)
- throws ServiceEntitlementException {
- // TODO(samalin): Add implementation
- return null;
- }
-
- /**
- * Retrieves service entitlement configurations for multiple app IDs in one HTTP request/response.
- * For on device service activation (ODSA) of eSIM for companion/primary devices, use {@link
- * #performEsimOdsa} instead.
- *
- * <p>Same with {@link #queryEntitlementStatus(String, ServiceEntitlementRequest)} except that
- * multiple "app" parameters will be set in the HTTP request, in the order as they appear in
- * parameter {@code appIds}.
- */
- public String queryEntitlementStatus(List<String> appIds, ServiceEntitlementRequest request)
- throws ServiceEntitlementException {
- // TODO(samalin): Add implementation
- return null;
- }
-
- /**
- * Performs on device service activation (ODSA) of eSIM for companion/primary devices.
- *
- * <p>Supported {@code appId}: {@link #APP_ODSA_COMPANION}, {@link #APP_ODSA_PRIMARY}.
- *
- * <p>Similar to {@link #queryEntitlementStatus(String, ServiceEntitlementRequest)}, this method
- * sends an HTTP GET request to entitlement server, responds to EAP-AKA challenge if needed, and
- * returns the raw configuration doc as a string. Additional parameters from {@code operation}
- * are set to the HTTP request. See {@link EsimOdsaOperation} for details.
- */
- public String performEsimOdsa(
- String appId, ServiceEntitlementRequest request, EsimOdsaOperation operation)
- throws ServiceEntitlementException {
- // TODO(samalin): Add implementation
- return null;
- }
-}
diff --git a/java/com/google/android/libraries/entitlement/ServiceEntitlementException.java b/java/com/google/android/libraries/entitlement/ServiceEntitlementException.java
deleted file mode 100644
index b632e2f..0000000
--- a/java/com/google/android/libraries/entitlement/ServiceEntitlementException.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Copyright (C) 2020 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.google.android.libraries.entitlement;
-
-/** Indicates errors happened in retrieving service entitlement configuration. */
-public class ServiceEntitlementException extends Exception {
- /** Unknown error. */
- public static final int ERROR_UNKNOWN = 0;
- /** Android telephony is unable to provide info like IMSI, e.g. when modem crashed. */
- public static final int ERROR_PHONE_NOT_AVAILABLE = 1;
- /**
- * 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;
- /**
- * 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;
- /**
- * 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 ServiceEntitlementException(
- int error, int httpStatus, String retryAfter, String message, Throwable cause) {}
-
- /** Returns the error code, see {@link #ERROR_*}. */
- public int getErrorCode() {
- // TODO(samalin): add implementation
- return ERROR_UNKNOWN;
- }
- /** Returns the HTTP status code returned by entitlement server; 0 if unavailable. */
- public int getHttpStatus() {
- // TODO(samalin): add implementation
- return ERROR_SEVER_NOT_CONNECTABLE;
- }
- /**
- * Returns the "Retry-After" header in HTTP response, often set with HTTP status code 503; an
- * empty string if unavailable.
- *
- * @return the HTTP-date or a number of seconds to delay, as defiend in RFC 7231:
- * https://tools.ietf.org/html/rfc7231#section-7.1.3
- */
- public String getRetryAfter() {
- // TODO(samalin): add implementation
- return null;
- }
-}
diff --git a/java/com/google/android/libraries/entitlement/ServiceEntitlementRequest.java b/java/com/google/android/libraries/entitlement/ServiceEntitlementRequest.java
deleted file mode 100644
index 032579c..0000000
--- a/java/com/google/android/libraries/entitlement/ServiceEntitlementRequest.java
+++ /dev/null
@@ -1,176 +0,0 @@
-/*
- * Copyright (C) 2020 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.google.android.libraries.entitlement;
-
-import com.google.auto.value.AutoValue;
-
-/** Service entitlement HTTP request parameters, as defiend in GSMA spec TS.43 section 2.2. */
-@AutoValue
-public abstract class ServiceEntitlementRequest {
- /** Disables notification token. */
- public static final int NOTICATION_ACTION_DISABLE = 0;
- /** Enables FCM notification token. */
- public static final int NOTICATION_ACTION_ENABLE_FCM = 2;
-
- /**
- * Returns the version of configuration currently stored on the client. Used by HTTP parameter
- * "vers".
- */
- public abstract int configurationVersion();
-
- /**
- * Returns the version of the entitlement specification. Used by HTTP parameter
- * "entitlement_version".
- */
- public abstract String entitlementVersion();
-
- /** Returns the authentication token. Used by HTTP parameter "token". */
- public abstract String authenticationToken();
-
- /**
- * Returns the unique identifier of the device like IMEI. Used by HTTP parameter "terminal_id".
- */
- public abstract String terminalId();
-
- /** Returns the OEM of the device. Used by HTTP parameter "terminal_vendor". */
- public abstract String terminalVendor();
-
- /** Returns the model of the device. Used by HTTP parameter "terminal_model". */
- public abstract String terminalModel();
-
- /** Returns the software version of the device. Used by HTTP parameter "terminal_sw_version". */
- public abstract String terminalSoftwareVersion();
-
- /**
- * Returns the name of the device application making the request. Used by HTTP parameter
- * "app_name".
- */
- public abstract String appName();
-
- /**
- * Returns the version of the device application making the request. Used by HTTP parameter
- * "app_version".
- */
- public abstract String appVersion();
-
- /**
- * Returns the FCM registration token used to register for entitlement configuration request from
- * network. Used by HTTP parameter "notif_token".
- */
- public abstract String notificationToken();
-
- /**
- * Returns the action associated with the FCM registration token. Used by HTTP parameter
- * "notif_action".
- *
- * @see #NOTICATION_ACTION_ENABLE_FCM
- * @see #NOTICATION_ACTION_DISABLE
- */
- public abstract int notificationAction();
-
- /** Returns a new {@link Builder} object. */
- public static Builder builder() {
- return new AutoValue_ServiceEntitlementRequest.Builder()
- .setConfigurationVersion(0)
- .setEntitlementVersion("2.0")
- .setNotificationAction(NOTICATION_ACTION_ENABLE_FCM);
- }
-
- /** Builder. */
- @AutoValue.Builder
- public abstract static class Builder {
- /**
- * Sets the version of configuration currently stored on the client. Used by HTTP parameter
- * "vers".
- *
- * <p>If not set, default to 0 indicating no existing configuration.
- */
- public abstract Builder setConfigurationVersion(int value);
- /**
- * Sets the version of configuration currently stored on the client. Used by HTTP parameter
- * "vers".
- *
- * <p>If not set, default to "2.0".
- */
- public abstract Builder setEntitlementVersion(String value);
- /**
- * Sets the authentication token. Used by HTTP parameter "token".
- *
- * <p>If not set, will trigger embedded EAP-AKA authentication as decribed in TS.43 section
- * 2.6.1.
- */
- public abstract Builder setAuthenticationToken(String value);
- /**
- * Sets the unique identifier of the device like IMEI. Used by HTTP parameter "terminal_id".
- *
- * <p>If not set, will use the device IMEI.
- */
- public abstract Builder setTerminalId(String value);
- /**
- * Sets the OEM of the device. Used by HTTP parameter "terminal_vendor".
- *
- * <p>If not set, will use {@link android.os.Build#MANUFACTURER}.
- */
- public abstract Builder setTerminalVendor(String value);
- /**
- * Sets the model of the device. Used by HTTP parameter "terminal_model".
- *
- * <p>If not set, will use {@link android.os.Build#MODEL}.
- */
- public abstract Builder setTerminalModel(String value);
- /**
- * Sets the software version of the device. Used by HTTP parameter "terminal_sw_version".
- *
- * <p>If not set, will use {@link android.os.Build.VERSION#BASE_OS}.
- */
- public abstract Builder setTerminalSoftwareVersion(String value);
- /**
- * Sets the name of the device application making the request. Used by HTTP parameter
- * "app_name".
- *
- * <p>Optional.
- */
- public abstract Builder setAppName(String value);
- /**
- * Sets the version of the device application making the request. Used by HTTP parameter
- * "app_version".
- *
- * <p>Optional.
- */
- public abstract Builder setAppVersion(String value);
- /**
- * Sets the FCM registration token used to register for entitlement configuration request from
- * network. Used by HTTP parameter "notif_token".
- *
- * <p>Optional.
- */
- public abstract Builder setNotificationToken(String value);
- /**
- * Sets the action associated with the FCM registration token. Used by HTTP parameter
- * "notif_action".
- *
- * <p>Required if a token is set with {@link #setNotificationToken}, and default to {@link
- * #NOTICATION_ACTION_ENABLE_FCM}; otherwise ignored.
- *
- * @see #NOTICATION_ACTION_ENABLE_FCM
- * @see #NOTICATION_ACTION_DISABLE
- */
- public abstract Builder setNotificationAction(int value);
-
- public abstract ServiceEntitlementRequest build();
- }
-}