/* * Copyright (C) 2021 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.libraries.entitlement.eapaka; import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_FAILURE; import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_EAP_AKA_SYNCHRONIZATION_FAILURE; import static com.android.libraries.entitlement.ServiceEntitlementException.ERROR_MALFORMED_HTTP_RESPONSE; import android.content.Context; import android.content.pm.PackageInfo; import android.net.Uri; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Log; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import com.android.libraries.entitlement.CarrierConfig; import com.android.libraries.entitlement.EsimOdsaOperation; import com.android.libraries.entitlement.ServiceEntitlementException; import com.android.libraries.entitlement.ServiceEntitlementRequest; import com.android.libraries.entitlement.http.HttpClient; import com.android.libraries.entitlement.http.HttpConstants.ContentType; import com.android.libraries.entitlement.http.HttpConstants.RequestMethod; import com.android.libraries.entitlement.http.HttpRequest; import com.android.libraries.entitlement.http.HttpResponse; import com.google.common.collect.ImmutableList; import com.google.common.net.HttpHeaders; import org.json.JSONException; import org.json.JSONObject; import java.util.List; public class EapAkaApi { private static final String TAG = "ServiceEntitlement"; public static final String EAP_CHALLENGE_RESPONSE = "eap-relay-packet"; private static final String VERS = "vers"; private static final String ENTITLEMENT_VERSION = "entitlement_version"; private static final String TERMINAL_ID = "terminal_id"; private static final String TERMINAL_VENDOR = "terminal_vendor"; private static final String TERMINAL_MODEL = "terminal_model"; private static final String TERMIAL_SW_VERSION = "terminal_sw_version"; private static final String APP = "app"; private static final String EAP_ID = "EAP_ID"; private static final String IMSI = "IMSI"; private static final String TOKEN = "token"; private static final String TEMPORARY_TOKEN = "temporary_token"; private static final String NOTIF_ACTION = "notif_action"; private static final String NOTIF_TOKEN = "notif_token"; private static final String APP_VERSION = "app_version"; private static final String APP_NAME = "app_name"; private static final String OPERATION = "operation"; private static final String OPERATION_TYPE = "operation_type"; private static final String OPERATION_TARGETS = "operation_targets"; private static final String COMPANION_TERMINAL_ID = "companion_terminal_id"; private static final String COMPANION_TERMINAL_VENDOR = "companion_terminal_vendor"; private static final String COMPANION_TERMINAL_MODEL = "companion_terminal_model"; private static final String COMPANION_TERMINAL_SW_VERSION = "companion_terminal_sw_version"; private static final String COMPANION_TERMINAL_FRIENDLY_NAME = "companion_terminal_friendly_name"; private static final String COMPANION_TERMINAL_SERVICE = "companion_terminal_service"; private static final String COMPANION_TERMINAL_ICCID = "companion_terminal_iccid"; private static final String COMPANION_TERMINAL_EID = "companion_terminal_eid"; private static final String TERMINAL_ICCID = "terminal_iccid"; private static final String TERMINAL_EID = "terminal_eid"; private static final String TARGET_TERMINAL_ID = "target_terminal_id"; // Non-standard params for Korean carriers private static final String TARGET_TERMINAL_IDS = "target_terminal_imeis"; private static final String TARGET_TERMINAL_ICCID = "target_terminal_iccid"; private static final String TARGET_TERMINAL_EID = "target_terminal_eid"; // Non-standard params for Korean carriers private static final String TARGET_TERMINAL_SERIAL_NUMBER = "target_terminal_sn"; // Non-standard params for Korean carriers private static final String TARGET_TERMINAL_MODEL = "target_terminal_model"; private static final String OLD_TERMINAL_ID = "old_terminal_id"; private static final String OLD_TERMINAL_ICCID = "old_terminal_iccid"; private static final String BOOST_TYPE = "boost_type"; // In case of EAP-AKA synchronization failure or another challenge, we try to authenticate for // at most three times. private static final int MAX_EAP_AKA_ATTEMPTS = 3; // Max TERMINAL_* string length according to GSMA RCC.14 section 2.4 private static final int MAX_TERMINAL_VENDOR_LENGTH = 4; private static final int MAX_TERMINAL_MODEL_LENGTH = 10; private static final int MAX_TERMINAL_SOFTWARE_VERSION_LENGTH = 20; private final Context mContext; private final int mSimSubscriptionId; private final HttpClient mHttpClient; private final String mBypassEapAkaResponse; private final String mAppVersion; public EapAkaApi( Context context, int simSubscriptionId, boolean saveHistory, String bypassEapAkaResponse) { this(context, simSubscriptionId, new HttpClient(saveHistory), bypassEapAkaResponse); } @VisibleForTesting EapAkaApi( Context context, int simSubscriptionId, HttpClient httpClient, String bypassEapAkaResponse) { this.mContext = context; this.mSimSubscriptionId = simSubscriptionId; this.mHttpClient = httpClient; this.mBypassEapAkaResponse = bypassEapAkaResponse; this.mAppVersion = getAppVersion(context); } /** * Retrieves HTTP response with the entitlement configuration doc though EAP-AKA authentication. * *
Implementation based on GSMA TS.43-v5.0 2.6.1.
*
* @throws ServiceEntitlementException when getting an unexpected http response.
*/
@NonNull
public HttpResponse queryEntitlementStatus(
ImmutableList The {@code eapAkaChallenge} should be the EAP-AKA challenge from server, and the follow-up
* request could contain:
*
* Implementation based on GSMA TS.43-v5.0 6.1.
*/
@NonNull
public HttpResponse performEsimOdsaOperation(
String appId,
CarrierConfig carrierConfig,
ServiceEntitlementRequest request,
EsimOdsaOperation odsaOperation)
throws ServiceEntitlementException {
Uri.Builder urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
appendParametersForAuthentication(urlBuilder, request);
appendParametersForServiceEntitlementRequest(urlBuilder, ImmutableList.of(appId), request);
appendParametersForEsimOdsaOperation(urlBuilder, odsaOperation);
if (!TextUtils.isEmpty(request.authenticationToken())
|| !TextUtils.isEmpty(request.temporaryToken())) {
// Fast Re-Authentication flow with pre-existing auth token
Log.d(TAG, "Fast Re-Authentication");
return httpGet(
urlBuilder.toString(),
carrierConfig,
request.acceptContentType(),
request.terminalVendor(),
request.terminalModel(),
request.terminalSoftwareVersion());
} else {
// Full Authentication flow
Log.d(TAG, "Full Authentication");
HttpResponse challengeResponse =
httpGet(
urlBuilder.toString(),
carrierConfig,
ServiceEntitlementRequest.ACCEPT_CONTENT_TYPE_JSON,
request.terminalVendor(),
request.terminalModel(),
request.terminalSoftwareVersion());
String eapAkaChallenge = getEapAkaChallenge(challengeResponse);
if (eapAkaChallenge == null) {
throw new ServiceEntitlementException(
ERROR_MALFORMED_HTTP_RESPONSE,
"Failed to parse EAP-AKA challenge: " + challengeResponse.body());
}
return respondToEapAkaChallenge(
carrierConfig,
eapAkaChallenge,
challengeResponse.cookies(),
MAX_EAP_AKA_ATTEMPTS,
request.acceptContentType(),
request.terminalVendor(),
request.terminalModel(),
request.terminalSoftwareVersion());
}
}
/**
* Retrieves the endpoint for OpenID Connect(OIDC) authentication.
*
* Implementation based on section 2.8.2 of TS.43
*
* The user should call {@link #queryEntitlementStatusFromOidc(String, CarrierConfig,
* String)} with the authentication result to retrieve the service entitlement configuration.
*/
@NonNull
public String acquireOidcAuthenticationEndpoint(
String appId, CarrierConfig carrierConfig, ServiceEntitlementRequest request)
throws ServiceEntitlementException {
Uri.Builder urlBuilder = Uri.parse(carrierConfig.serverUrl()).buildUpon();
appendParametersForServiceEntitlementRequest(urlBuilder, ImmutableList.of(appId), request);
HttpResponse response =
httpGet(
urlBuilder.toString(),
carrierConfig,
request.acceptContentType(),
request.terminalVendor(),
request.terminalModel(),
request.terminalSoftwareVersion());
return response.location();
}
/**
* Retrieves the HTTP response with the service entitlement configuration from OIDC
* authentication result.
*
* Implementation based on section 2.8.2 of TS.43.
*
* {@link #acquireOidcAuthenticationEndpoint} must be called before calling this method.
*/
@NonNull
public HttpResponse queryEntitlementStatusFromOidc(
String url, CarrierConfig carrierConfig, ServiceEntitlementRequest request)
throws ServiceEntitlementException {
Uri.Builder urlBuilder = Uri.parse(url).buildUpon();
return httpGet(
urlBuilder.toString(),
carrierConfig,
request.acceptContentType(),
request.terminalVendor(),
request.terminalModel(),
request.terminalSoftwareVersion());
}
private void appendParametersForAuthentication(
Uri.Builder urlBuilder, ServiceEntitlementRequest request) {
TelephonyManager telephonyManager =
mContext.getSystemService(TelephonyManager.class)
.createForSubscriptionId(mSimSubscriptionId);
if (!TextUtils.isEmpty(request.authenticationToken())) {
// IMSI and token required for fast AuthN.
urlBuilder
.appendQueryParameter(IMSI, telephonyManager.getSubscriberId())
.appendQueryParameter(TOKEN, request.authenticationToken());
} else if (!TextUtils.isEmpty(request.temporaryToken())) {
// temporary_token required for fast AuthN.
urlBuilder.appendQueryParameter(TEMPORARY_TOKEN, request.temporaryToken());
} else {
// EAP_ID required for initial AuthN
urlBuilder.appendQueryParameter(
EAP_ID,
getImsiEap(
telephonyManager.getSimOperator(), telephonyManager.getSubscriberId()));
}
}
private void appendParametersForServiceEntitlementRequest(
Uri.Builder urlBuilder,
ImmutableList {@code 0
*
*
* @return Challenge response from server whose content type is JSON
*/
@NonNull
private HttpResponse respondToEapAkaChallenge(
CarrierConfig carrierConfig,
String eapAkaChallenge,
ImmutableList