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.Preconditions.checkNotNull; 20 import static com.google.common.base.Strings.nullToEmpty; 21 22 import android.content.Context; 23 import android.os.Build; 24 import android.telephony.SubscriptionInfo; 25 import android.telephony.SubscriptionManager; 26 import android.telephony.TelephonyManager; 27 import android.text.TextUtils; 28 import android.util.Log; 29 30 import androidx.annotation.NonNull; 31 import androidx.annotation.Nullable; 32 import androidx.annotation.VisibleForTesting; 33 34 import com.android.libraries.entitlement.http.HttpResponse; 35 import com.android.libraries.entitlement.utils.Ts43Constants; 36 import com.android.libraries.entitlement.utils.Ts43Constants.AppId; 37 import com.android.libraries.entitlement.utils.Ts43XmlDoc; 38 39 import com.google.auto.value.AutoValue; 40 import com.google.common.collect.ImmutableList; 41 42 import java.net.URL; 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 @Nullable 117 private ServiceEntitlement mServiceEntitlement; 118 119 /** 120 * Ts43Authentication constructor. 121 * 122 * @param context The application context. 123 * @param entitlementServerAddress The entitlement server address. 124 * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}. 125 * If {@code null}, version {@code "2.0"} will be used by default. 126 * 127 * @throws NullPointerException wWhen {@code context} or {@code entitlementServerAddress} is 128 * {@code null}. 129 */ Ts43Authentication(@onNull Context context, @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion)130 public Ts43Authentication(@NonNull Context context, @NonNull URL entitlementServerAddress, 131 @Nullable String entitlementVersion) { 132 mContext = checkNotNull(context); 133 mEntitlementServerAddress = checkNotNull(entitlementServerAddress); 134 135 if (entitlementVersion != null) { 136 mEntitlementVersion = entitlementVersion; 137 } else { 138 mEntitlementVersion = Ts43Constants.DEFAULT_ENTITLEMENT_VERSION; 139 } 140 } 141 142 /** 143 * Get the authentication token for TS.43 operations with EAP-AKA described in TS.43 144 * Service Entitlement Configuration section 2.8.1. 145 * 146 * @param slotIndex The logical SIM slot index involved in ODSA operation. 147 * See {@link SubscriptionInfo#getSubscriptionId()}. 148 149 * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi, 150 * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service 151 * Entitlement Configuration section 2.3. 152 * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET 153 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 154 * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET 155 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 156 * @param acceptContentType The accepted content type of the HTTP response, or {@code null} to 157 * use the default. 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, @Nullable String acceptContentType)169 public Ts43AuthToken getAuthToken(int slotIndex, @NonNull @AppId String appId, 170 @Nullable String appName, @Nullable String appVersion, 171 @Nullable String acceptContentType) 172 throws ServiceEntitlementException { 173 checkNotNull(appId); 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.Builder builder = 190 ServiceEntitlementRequest.builder() 191 .setEntitlementVersion(mEntitlementVersion) 192 .setTerminalId(nullToEmpty(imei)) 193 .setAppName(nullToEmpty(appName)) 194 .setAppVersion(nullToEmpty(appVersion)); 195 if (acceptContentType != null) { 196 builder.setAcceptContentType(acceptContentType); 197 } 198 ServiceEntitlementRequest request = builder.build(); 199 CarrierConfig carrierConfig = CarrierConfig.builder() 200 .setServerUrl(mEntitlementServerAddress.toString()) 201 .build(); 202 203 if (mServiceEntitlement == null) { 204 int subId = SubscriptionManager.DEFAULT_SUBSCRIPTION_ID; 205 if (Build.VERSION.SDK_INT < 34) { 206 SubscriptionManager subscriptionManager = 207 mContext.getSystemService(SubscriptionManager.class); 208 int[] subIds = subscriptionManager.getSubscriptionIds(slotIndex); 209 if (subIds != null && subIds.length > 0) { 210 subId = subIds[0]; 211 } 212 } else { 213 subId = SubscriptionManager.getSubscriptionId(slotIndex); 214 } 215 mServiceEntitlement = new ServiceEntitlement(mContext, carrierConfig, subId); 216 } 217 218 // Get the full HTTP response instead of just the body so we can reuse the same cookies. 219 HttpResponse response; 220 String rawXml; 221 try { 222 response = mServiceEntitlement.getEntitlementStatusResponse( 223 ImmutableList.of(appId), request); 224 rawXml = response == null ? "" : response.body(); 225 Log.d(TAG, "getAuthToken: rawXml=" + rawXml); 226 } catch (ServiceEntitlementException e) { 227 Log.w(TAG, "Failed to get authentication token. e=" + e); 228 throw e; 229 } 230 231 ImmutableList<String> cookies = response == null ? ImmutableList.of() : response.cookies(); 232 233 Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml); 234 String authToken = ts43XmlDoc.get( 235 ImmutableList.of(Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.TOKEN); 236 if (TextUtils.isEmpty(authToken)) { 237 Log.w(TAG, "Failed to parse authentication token"); 238 throw new ServiceEntitlementException( 239 ServiceEntitlementException.ERROR_TOKEN_NOT_AVAILABLE, 240 "Failed to parse authentication token"); 241 } 242 243 String validityString = nullToEmpty(ts43XmlDoc.get(ImmutableList.of( 244 Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.VALIDITY)); 245 long validity; 246 try { 247 validity = Long.parseLong(validityString); 248 } catch (NumberFormatException e) { 249 validity = Ts43AuthToken.VALIDITY_NOT_AVAILABLE; 250 } 251 252 return Ts43AuthToken.create(authToken, cookies, validity); 253 } 254 255 /** 256 * Get the URL of OIDC (OpenID Connect) server as described in TS.43 Service Entitlement 257 * Configuration section 2.8.2. 258 * 259 * The caller is expected to present the content of the URL to the user to proceed the 260 * authentication process. After that the caller can call {@link #getAuthToken(URL)} 261 * to get the authentication token. 262 * 263 * @param slotIndex The logical SIM slot index involved in ODSA operation. 264 * @param entitlementServerAddress The entitlement server address. 265 * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}. 266 * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi, 267 * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service 268 * Entitlement Configuration section 2.3. 269 * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET 270 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 271 * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET 272 * request in GSMA TS.43 Service Entitlement Configuration section 2.3. 273 * @param acceptContentType The accepted content type of the HTTP response, or {@code null} to 274 * use the default. 275 * 276 * @return The URL of OIDC server with all the required parameters for client to launch a 277 * user interface for users to interact with the authentication process. The parameters in URL 278 * include {@code client_id}, {@code redirect_uri}, {@code state}, and {@code nonce}. 279 * 280 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 281 * error from the server, the error code can be retrieved by 282 * {@link ServiceEntitlementException#getHttpStatus()} 283 */ 284 @NonNull getOidcAuthServer(@onNull Context context, int slotIndex, @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion, @NonNull @AppId String appId, @Nullable String appName, @Nullable String appVersion, @Nullable String acceptContentType)285 public URL getOidcAuthServer(@NonNull Context context, int slotIndex, 286 @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion, 287 @NonNull @AppId String appId, @Nullable String appName, @Nullable String appVersion, 288 @Nullable String acceptContentType) throws ServiceEntitlementException { 289 throw new UnsupportedOperationException("Not implemented yet"); 290 } 291 292 /** 293 * Get the authentication token for TS.43 operations with OIDC (OpenID Connect) described in 294 * TS.43 Service Entitlement Configuration section 2.8.2. 295 * 296 * @param aesUrl The AES URL used to retrieve auth token. The parameters in the URL include 297 * the OIDC auth code {@code code} and {@code state}. 298 * 299 * @return The authentication token. 300 * 301 * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response 302 * error from the server, the error code can be retrieved by 303 * {@link ServiceEntitlementException#getHttpStatus()} 304 */ 305 @NonNull getAuthToken(@onNull URL aesUrl)306 public Ts43AuthToken getAuthToken(@NonNull URL aesUrl) 307 throws ServiceEntitlementException { 308 throw new UnsupportedOperationException("Not implemented yet"); 309 } 310 } 311