1 /* 2 * Copyright (C) 2023 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.libraries.entitlement; 18 19 import static com.google.common.base.Strings.nullToEmpty; 20 21 import android.content.Context; 22 import android.os.Build; 23 import android.telephony.SubscriptionInfo; 24 import android.telephony.SubscriptionManager; 25 import android.telephony.TelephonyManager; 26 import android.text.TextUtils; 27 import android.util.Log; 28 29 import androidx.annotation.NonNull; 30 import androidx.annotation.Nullable; 31 import androidx.annotation.VisibleForTesting; 32 33 import com.android.libraries.entitlement.http.HttpResponse; 34 import com.android.libraries.entitlement.utils.Ts43Constants; 35 import com.android.libraries.entitlement.utils.Ts43Constants.AppId; 36 import com.android.libraries.entitlement.utils.Ts43XmlDoc; 37 38 import com.google.auto.value.AutoValue; 39 import com.google.common.collect.ImmutableList; 40 41 import java.net.URL; 42 import java.util.Objects; 43 44 /** 45 * The class responsible for TS.43 authentication process. 46 */ 47 public class Ts43Authentication { 48 private static final String TAG = "Ts43Auth"; 49 50 /** 51 * The authentication token for TS.43 operation. 52 */ 53 @AutoValue 54 public abstract static class Ts43AuthToken { 55 /** 56 * Indicating the validity of token is not available. 57 */ 58 public static long VALIDITY_NOT_AVAILABLE = -1; 59 60 /** 61 * The authentication token for TS.43 operations. 62 */ 63 @NonNull token()64 public abstract String token(); 65 66 /** 67 * The list of cookies from the {@code Set-Cookie} header of the TS.43 response. 68 */ 69 @NonNull cookies()70 public abstract ImmutableList<String> cookies(); 71 72 /** 73 * Indicates the validity of the token. Note this value is server dependent. The client is 74 * expected to interpret this value itself. 75 */ validity()76 public abstract long validity(); 77 78 /** 79 * Create the {@link Ts43AuthToken} object. 80 * 81 * @param token The authentication token for TS.43 operations. 82 * @param cookie The list of cookies from the {@code Set-Cookie} header. 83 * @param validity Indicates the validity of the token. Note this value is server 84 * dependent. If not available, set to {@link #VALIDITY_NOT_AVAILABLE}. 85 * 86 * @return The {@link Ts43AuthToken} object. 87 */ create(@onNull String token, @NonNull ImmutableList<String> cookie, long validity)88 public static Ts43AuthToken create(@NonNull String token, 89 @NonNull ImmutableList<String> cookie, long validity) { 90 return new AutoValue_Ts43Authentication_Ts43AuthToken(token, cookie, validity); 91 } 92 } 93 94 /** 95 * The application context. 96 */ 97 @NonNull 98 private final Context mContext; 99 100 /** 101 * The entitlement server address. 102 */ 103 @NonNull 104 private final URL mEntitlementServerAddress; 105 106 /** 107 * The TS.43 entitlement version to use. For example, {@code "9.0"}. 108 */ 109 @NonNull 110 private final String mEntitlementVersion; 111 112 /** 113 * For test mocking only. 114 */ 115 @VisibleForTesting 116 private ServiceEntitlement mServiceEntitlement; 117 118 /** 119 * Ts43Authentication constructor. 120 * 121 * @param context The application context. 122 * @param entitlementServerAddress The entitlement server address. 123 * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}. 124 * If {@code null}, version {@code "2.0"} will be used by default. 125 * 126 * @throws NullPointerException wWhen {@code context} or {@code entitlementServerAddress} is 127 * {@code null}. 128 */ Ts43Authentication(@onNull Context context, @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion)129 public Ts43Authentication(@NonNull Context context, @NonNull URL entitlementServerAddress, 130 @Nullable String entitlementVersion) { 131 Objects.requireNonNull(context, "context is null"); 132 Objects.requireNonNull(entitlementServerAddress, "entitlementServerAddress is null."); 133 134 mContext = context; 135 mEntitlementServerAddress = entitlementServerAddress; 136 137 if (entitlementVersion != null) { 138 mEntitlementVersion = entitlementVersion; 139 } else { 140 mEntitlementVersion = Ts43Constants.DEFAULT_ENTITLEMENT_VERSION; 141 } 142 } 143 144 /** 145 * Get the authentication token for TS.43 operations with EAP-AKA described in TS.43 146 * Service Entitlement Configuration section 2.8.1. 147 * 148 * @param slotIndex The logical SIM slot index involved in ODSA operation. 149 * See {@link SubscriptionInfo#getSubscriptionId()}. 150 151 * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi, 152 * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service 153 * Entitlement Configuration section 2.3. 154 * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET 155 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 156 * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET 157 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 158 * 159 * @return The authentication token. 160 * 161 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 162 * error from the server, the error code can be retrieved by 163 * {@link ServiceEntitlementException#getHttpStatus()}. 164 * @throws IllegalArgumentException when {@code slotIndex} or {@code appId} is invalid. 165 * @throws NullPointerException when {@code context}, {@code entitlementServerAddress}, or 166 * {@code appId} is {@code null}. 167 */ 168 @NonNull getAuthToken(int slotIndex, @NonNull @AppId String appId, @Nullable String appName, @Nullable String appVersion)169 public Ts43AuthToken getAuthToken(int slotIndex, @NonNull @AppId String appId, 170 @Nullable String appName, @Nullable String appVersion) 171 throws ServiceEntitlementException { 172 Objects.requireNonNull(appId, "appId is null"); 173 174 if (!Ts43Constants.isValidAppId(appId)) { 175 throw new IllegalArgumentException("getAuthToken: invalid app id " + appId); 176 } 177 178 String imei = null; 179 TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class); 180 if (telephonyManager != null) { 181 if (slotIndex < 0 || slotIndex >= telephonyManager.getActiveModemCount()) { 182 throw new IllegalArgumentException("getAuthToken: invalid slot index " + slotIndex); 183 } 184 imei = telephonyManager.getImei(slotIndex); 185 } 186 187 // Build the HTTP request. The default params are specified in 188 // ServiceEntitlementRequest.builder() already. 189 ServiceEntitlementRequest request = 190 ServiceEntitlementRequest.builder() 191 .setEntitlementVersion(mEntitlementVersion) 192 .setTerminalId(imei) 193 .setAppName(appName) 194 .setAppVersion(appVersion) 195 .build(); 196 CarrierConfig carrierConfig = CarrierConfig.builder() 197 .setServerUrl(mEntitlementServerAddress.toString()) 198 .build(); 199 200 if (mServiceEntitlement == null) { 201 int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; 202 if (Build.VERSION.SDK_INT < 34) { 203 SubscriptionManager subscriptionManager = 204 mContext.getSystemService(SubscriptionManager.class); 205 int[] subIds = subscriptionManager.getSubscriptionIds(slotIndex); 206 if (subIds != null && subIds.length > 0) { 207 subId = subIds[0]; 208 } 209 } else { 210 subId = SubscriptionManager.getSubscriptionId(slotIndex); 211 } 212 mServiceEntitlement = new ServiceEntitlement(mContext, carrierConfig, subId); 213 } 214 215 // Get the full HTTP response instead of just the body so we can reuse the same cookies. 216 HttpResponse response; 217 String rawXml; 218 try { 219 response = mServiceEntitlement.getEntitlementStatusResponse( 220 ImmutableList.of(appId), request); 221 rawXml = response == null ? null : response.body(); 222 Log.d(TAG, "getAuthToken: rawXml=" + rawXml); 223 } catch (ServiceEntitlementException e) { 224 Log.w(TAG, "Failed to get authentication token. e=" + e); 225 throw e; 226 } 227 228 ImmutableList<String> cookies = response == null ? ImmutableList.of() : response.cookies(); 229 230 Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml); 231 String authToken = ts43XmlDoc.get( 232 ImmutableList.of(Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.TOKEN); 233 if (TextUtils.isEmpty(authToken)) { 234 Log.w(TAG, "Failed to parse authentication token"); 235 throw new ServiceEntitlementException( 236 ServiceEntitlementException.ERROR_TOKEN_NOT_AVAILABLE, 237 "Failed to parse authentication token"); 238 } 239 240 String validityString = nullToEmpty(ts43XmlDoc.get(ImmutableList.of( 241 Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.VALIDITY)); 242 long validity; 243 try { 244 validity = Long.parseLong(validityString); 245 } catch (NumberFormatException e) { 246 validity = Ts43AuthToken.VALIDITY_NOT_AVAILABLE; 247 } 248 249 return Ts43AuthToken.create(authToken, cookies, validity); 250 } 251 252 /** 253 * Get the URL of OIDC (OpenID Connect) server as described in TS.43 Service Entitlement 254 * Configuration section 2.8.2. 255 * 256 * The caller is expected to present the content of the URL to the user to proceed the 257 * authentication process. After that the caller can call {@link #getAuthToken(URL)} 258 * to get the authentication token. 259 * 260 * @param slotIndex The logical SIM slot index involved in ODSA operation. 261 * @param entitlementServerAddress The entitlement server address. 262 * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}. 263 * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi, 264 * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service 265 * Entitlement Configuration section 2.3. 266 * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET 267 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 268 * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET 269 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 270 * 271 * @return The URL of OIDC server with all the required parameters for client to launch a 272 * user interface for users to interact with the authentication process. The parameters in URL 273 * include {@code client_id}, {@code redirect_uri}, {@code state}, and {@code nonce}. 274 * 275 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 276 * error from the server, the error code can be retrieved by 277 * {@link ServiceEntitlementException#getHttpStatus()} 278 */ 279 @NonNull getOidcAuthServer(@onNull Context context, int slotIndex, @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion, @NonNull @AppId String appId, @Nullable String appName, @Nullable String appVersion)280 public URL getOidcAuthServer(@NonNull Context context, int slotIndex, 281 @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion, 282 @NonNull @AppId String appId, @Nullable String appName, @Nullable String appVersion) 283 throws ServiceEntitlementException { 284 return null; 285 } 286 287 /** 288 * Get the authentication token for TS.43 operations with OIDC (OpenID Connect) described in 289 * TS.43 Service Entitlement Configuration section 2.8.2. 290 * 291 * @param aesUrl The AES URL used to retrieve auth token. The parameters in the URL include 292 * the OIDC auth code {@code code} and {@code state}. 293 * 294 * @return The authentication token. 295 * 296 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 297 * error from the server, the error code can be retrieved by 298 * {@link ServiceEntitlementException#getHttpStatus()} 299 */ 300 @NonNull getAuthToken(@onNull URL aesUrl)301 public Ts43AuthToken getAuthToken(@NonNull URL aesUrl) 302 throws ServiceEntitlementException { 303 return null; 304 } 305 } 306