aboutsummaryrefslogtreecommitdiff
path: root/java/com/android/libraries/entitlement/Ts43Authentication.java
blob: 29d0cb923e030ea944fad623c5907a407ee5a65e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
/*
 * Copyright (C) 2023 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;

import static com.google.common.base.Strings.nullToEmpty;

import android.content.Context;
import android.telephony.SubscriptionInfo;
import android.telephony.SubscriptionManager;
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.http.HttpResponse;
import com.android.libraries.entitlement.utils.Ts43Constants;
import com.android.libraries.entitlement.utils.Ts43Constants.AppId;
import com.android.libraries.entitlement.utils.Ts43XmlDoc;

import com.google.auto.value.AutoValue;
import com.google.common.collect.ImmutableList;

import java.net.URL;
import java.util.Objects;

/**
 * The class responsible for TS.43 authentication process.
 */
public class Ts43Authentication {
    private static final String TAG = "Ts43Auth";

    /**
     * The authentication token for TS.43 operation.
     */
    @AutoValue
    public abstract static class Ts43AuthToken {
        /**
         * Indicating the validity of token is not available.
         */
        public static long VALIDITY_NOT_AVAILABLE = -1;

        /**
         * The authentication token for TS.43 operations.
         */
        @NonNull
        public abstract String token();

        /**
         * The list of cookies from the {@code Set-Cookie} header of the TS.43 response.
         */
        @NonNull
        public abstract ImmutableList<String> cookies();

        /**
         * Indicates the validity of the token. Note this value is server dependent. The client is
         * expected to interpret this value itself.
         */
        public abstract long validity();

        /**
         * Create the {@link Ts43AuthToken} object.
         *
         * @param token The authentication token for TS.43 operations.
         * @param cookie The list of cookies from the {@code Set-Cookie} header.
         * @param validity Indicates the validity of the token. Note this value is server
         * dependent. If not available, set to {@link #VALIDITY_NOT_AVAILABLE}.
         *
         * @return The {@link Ts43AuthToken} object.
         */
        public static Ts43AuthToken create(@NonNull String token,
                @NonNull ImmutableList<String> cookie, long validity) {
            return new AutoValue_Ts43Authentication_Ts43AuthToken(token, cookie, validity);
        }
    }

    /**
     * The application context.
     */
    @NonNull
    private final Context mContext;

    /**
     * The entitlement server address.
     */
    @NonNull
    private final URL mEntitlementServerAddress;

    /**
     * The TS.43 entitlement version to use. For example, {@code "9.0"}.
     */
    @NonNull
    private final String mEntitlementVersion;

    /**
     * For test mocking only.
     */
    @VisibleForTesting
    private ServiceEntitlement mServiceEntitlement;

    /**
     * Ts43Authentication constructor.
     *
     * @param context The application context.
     * @param entitlementServerAddress The entitlement server address.
     * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}.
     * If {@code null}, version {@code "2.0"} will be used by default.
     *
     * @throws NullPointerException wWhen {@code context} or {@code entitlementServerAddress} is
     * {@code null}.
     */
    public Ts43Authentication(@NonNull Context context, @NonNull URL entitlementServerAddress,
            @Nullable String entitlementVersion) {
        Objects.requireNonNull(context, "context is null");
        Objects.requireNonNull(entitlementServerAddress, "entitlementServerAddress is null.");

        mContext = context;
        mEntitlementServerAddress = entitlementServerAddress;

        if (entitlementVersion != null) {
            mEntitlementVersion = entitlementVersion;
        } else {
            mEntitlementVersion = Ts43Constants.DEFAULT_ENTITLEMENT_VERSION;
        }
    }

    /**
     * Get the authentication token for TS.43 operations with EAP-AKA described in TS.43
     * Service Entitlement Configuration section 2.8.1.
     *
     * @param slotIndex The logical SIM slot index involved in ODSA operation.
     * See {@link SubscriptionInfo#getSubscriptionId()}.

     * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi,
     * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service
     * Entitlement Configuration section 2.3.
     * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET
     * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
     * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET
     * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
     *
     * @return The authentication token.
     *
     * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
     * error from the server, the error code can be retrieved by
     * {@link ServiceEntitlementException#getHttpStatus()}.
     * @throws IllegalArgumentException when {@code slotIndex} or {@code appId} is invalid.
     * @throws NullPointerException when {@code context}, {@code entitlementServerAddress}, or
     * {@code appId} is {@code null}.
     */
    @NonNull
    public Ts43AuthToken getAuthToken(int slotIndex, @NonNull @AppId String appId,
            @Nullable String appName, @Nullable String appVersion)
            throws ServiceEntitlementException {
        Objects.requireNonNull(appId, "appId is null");

        if (!Ts43Constants.isValidAppId(appId)) {
            throw new IllegalArgumentException("getAuthToken: invalid app id " + appId);
        }

        String imei = null;
        TelephonyManager telephonyManager = mContext.getSystemService(TelephonyManager.class);
        if (telephonyManager != null) {
            if (slotIndex < 0 || slotIndex >= telephonyManager.getActiveModemCount()) {
                throw new IllegalArgumentException("getAuthToken: invalid slot index " + slotIndex);
            }
            imei = telephonyManager.getImei(slotIndex);
        }

        // Build the HTTP request. The default params are specified in
        // ServiceEntitlementRequest.builder() already.
        ServiceEntitlementRequest request =
                ServiceEntitlementRequest.builder()
                        .setEntitlementVersion(mEntitlementVersion)
                        .setTerminalId(imei)
                        .setAppName(appName)
                        .setAppVersion(appVersion)
                        .build();
        CarrierConfig carrierConfig = CarrierConfig.builder()
                .setServerUrl(mEntitlementServerAddress.toString())
                .build();

        if (mServiceEntitlement == null) {
            mServiceEntitlement = new ServiceEntitlement(mContext, carrierConfig,
                    SubscriptionManager.getSubscriptionId(slotIndex));
        }

        // Get the full HTTP response instead of just the body so we can reuse the same cookies.
        HttpResponse response;
        String rawXml;
        try {
            response = mServiceEntitlement.getEntitlementStatusResponse(
                    ImmutableList.of(appId), request);
            rawXml = response == null ? null : response.body();
            Log.d(TAG, "getAuthToken: rawXml=" + rawXml);
        } catch (ServiceEntitlementException e) {
            Log.w(TAG, "Failed to get authentication token. e=" + e);
            throw e;
        }

        ImmutableList<String> cookies = response == null ? ImmutableList.of() : response.cookies();

        Ts43XmlDoc ts43XmlDoc = new Ts43XmlDoc(rawXml);
        String authToken = ts43XmlDoc.get(
                ImmutableList.of(Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.TOKEN);
        if (TextUtils.isEmpty(authToken)) {
            Log.w(TAG, "Failed to parse authentication token");
            throw new ServiceEntitlementException(
                    ServiceEntitlementException.ERROR_TOKEN_NOT_AVAILABLE,
                    "Failed to parse authentication token");
        }

        String validityString = nullToEmpty(ts43XmlDoc.get(ImmutableList.of(
                Ts43XmlDoc.CharacteristicType.TOKEN), Ts43XmlDoc.Parm.VALIDITY));
        long validity;
        try {
            validity = Long.parseLong(validityString);
        } catch (NumberFormatException e) {
            validity = Ts43AuthToken.VALIDITY_NOT_AVAILABLE;
        }

        return Ts43AuthToken.create(authToken, cookies, validity);
    }

    /**
     * Get the URL of OIDC (OpenID Connect) server as described in TS.43 Service Entitlement
     * Configuration section 2.8.2.
     *
     * The caller is expected to present the content of the URL to the user to proceed the
     * authentication process. After that the caller can call {@link #getAuthToken(URL)}
     * to get the authentication token.
     *
     * @param slotIndex The logical SIM slot index involved in ODSA operation.
     * @param entitlementServerAddress The entitlement server address.
     * @param entitlementVersion The TS.43 entitlement version to use. For example, {@code "9.0"}.
     * @param appId Application id. For example, {@link Ts43Constants#APP_VOWIFI} for VoWifi,
     * {@link Ts43Constants#APP_ODSA_PRIMARY} for ODSA primary device. Refer GSMA to Service
     * Entitlement Configuration section 2.3.
     * @param appName The calling client's package name. Used for {@code app_name} in HTTP GET
     * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
     * @param appVersion The calling client's version. Used for {@code app_version} in HTTP GET
     * request in GSMA TS.43 Service Entitlement Configuration section 2.3.
     *
     * @return The URL of OIDC server with all the required parameters for client to launch a
     * user interface for users to interact with the authentication process. The parameters in URL
     * include {@code client_id}, {@code redirect_uri}, {@code state}, and {@code nonce}.
     *
     * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
     * error from the server, the error code can be retrieved by
     * {@link ServiceEntitlementException#getHttpStatus()}
     */
    @NonNull
    public URL getOidcAuthServer(@NonNull Context context, int slotIndex,
            @NonNull URL entitlementServerAddress, @Nullable String entitlementVersion,
            @NonNull @AppId String appId, @Nullable String appName, @Nullable String appVersion)
            throws ServiceEntitlementException {
        return null;
    }

    /**
     * Get the authentication token for TS.43 operations with OIDC (OpenID Connect) described in
     * TS.43 Service Entitlement Configuration section 2.8.2.
     *
     * @param aesUrl The AES URL used to retrieve auth token. The parameters in the URL include
     * the OIDC auth code {@code code} and {@code state}.
     *
     * @return The authentication token.
     *
     * @throws ServiceEntitlementException The exception for error case. If it's an HTTP response
     * error from the server, the error code can be retrieved by
     * {@link ServiceEntitlementException#getHttpStatus()}
     */
    @NonNull
    public Ts43AuthToken getAuthToken(@NonNull URL aesUrl)
            throws ServiceEntitlementException {
        return null;
    }
}