• 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.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