• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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