1/**
2 * Copyright (c) 2017, 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
17package android.net.wifi.hotspot2.pps;
18
19import android.os.Parcel;
20import android.os.Parcelable;
21import android.text.TextUtils;
22import android.util.Base64;
23import android.util.Log;
24
25import java.nio.charset.StandardCharsets;
26import java.util.Arrays;
27import java.util.Objects;
28
29/**
30 * Class representing configuration parameters for subscription or policy update in
31 * PerProviderSubscription (PPS) Management Object (MO) tree.  This is used by both
32 * PerProviderSubscription/Policy/PolicyUpdate and PerProviderSubscription/SubscriptionUpdate
33 * subtree.
34 *
35 * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
36 * Release 2 Technical Specification.
37 *
38 * @hide
39 */
40public final class UpdateParameter implements Parcelable {
41    private static final String TAG = "UpdateParameter";
42
43    /**
44     * Value indicating policy update is not applicable.  Thus, never check with policy server
45     * for updates.
46     */
47    public static final long UPDATE_CHECK_INTERVAL_NEVER = 0xFFFFFFFFL;
48
49    /**
50     * Valid string for UpdateMethod.
51     */
52    public static final String UPDATE_METHOD_OMADM = "OMA-DM-ClientInitiated";
53    public static final String UPDATE_METHOD_SSP = "SSP-ClientInitiated";
54
55    /**
56     * Valid string for Restriction.
57     */
58    public static final String UPDATE_RESTRICTION_HOMESP = "HomeSP";
59    public static final String UPDATE_RESTRICTION_ROAMING_PARTNER = "RoamingPartner";
60    public static final String UPDATE_RESTRICTION_UNRESTRICTED = "Unrestricted";
61
62    /**
63     * Maximum bytes for URI string.
64     */
65    private static final int MAX_URI_BYTES = 1023;
66
67    /**
68     * Maximum bytes for URI string.
69     */
70    private static final int MAX_URL_BYTES = 1023;
71
72    /**
73     * Maximum bytes for username.
74     */
75    private static final int MAX_USERNAME_BYTES = 63;
76
77    /**
78     * Maximum bytes for password.
79     */
80    private static final int MAX_PASSWORD_BYTES = 255;
81
82    /**
83     * Number of bytes for certificate SHA-256 fingerprint byte array.
84     */
85    private static final int CERTIFICATE_SHA256_BYTES = 32;
86
87    /**
88     * This specifies how often the mobile device shall check with policy server for updates.
89     *
90     * Using Long.MIN_VALUE to indicate unset value.
91     */
92    private long mUpdateIntervalInMinutes = Long.MIN_VALUE;
93    public void setUpdateIntervalInMinutes(long updateIntervalInMinutes) {
94        mUpdateIntervalInMinutes = updateIntervalInMinutes;
95    }
96    public long getUpdateIntervalInMinutes() {
97        return mUpdateIntervalInMinutes;
98    }
99
100    /**
101     * The method used to update the policy.  Permitted values are "OMA-DM-ClientInitiated"
102     * and "SPP-ClientInitiated".
103     */
104    private String mUpdateMethod = null;
105    public void setUpdateMethod(String updateMethod) {
106        mUpdateMethod = updateMethod;
107    }
108    public String getUpdateMethod() {
109        return mUpdateMethod;
110    }
111
112    /**
113     * This specifies the hotspots at which the subscription update is permitted.  Permitted
114     * values are "HomeSP", "RoamingPartner", or "Unrestricted";
115     */
116    private String mRestriction = null;
117    public void setRestriction(String restriction) {
118        mRestriction = restriction;
119    }
120    public String getRestriction() {
121        return mRestriction;
122    }
123
124    /**
125     * The URI of the update server.
126     */
127    private String mServerUri = null;
128    public void setServerUri(String serverUri) {
129        mServerUri = serverUri;
130    }
131    public String getServerUri() {
132        return mServerUri;
133    }
134
135    /**
136     * Username used to authenticate with the policy server.
137     */
138    private String mUsername = null;
139    public void setUsername(String username) {
140        mUsername = username;
141    }
142    public String getUsername() {
143        return mUsername;
144    }
145
146    /**
147     * Base64 encoded password used to authenticate with the policy server.
148     */
149    private String mBase64EncodedPassword = null;
150    public void setBase64EncodedPassword(String password) {
151        mBase64EncodedPassword = password;
152    }
153    public String getBase64EncodedPassword() {
154        return mBase64EncodedPassword;
155    }
156
157    /**
158     * HTTPS URL for retrieving certificate for trust root.  The trust root is used to validate
159     * policy server's identity.
160     */
161    private String mTrustRootCertUrl = null;
162    public void setTrustRootCertUrl(String trustRootCertUrl) {
163        mTrustRootCertUrl = trustRootCertUrl;
164    }
165    public String getTrustRootCertUrl() {
166        return mTrustRootCertUrl;
167    }
168
169    /**
170     * SHA-256 fingerprint of the certificate located at {@link #trustRootCertUrl}
171     */
172    private byte[] mTrustRootCertSha256Fingerprint = null;
173    public void setTrustRootCertSha256Fingerprint(byte[] fingerprint) {
174        mTrustRootCertSha256Fingerprint = fingerprint;
175    }
176    public byte[] getTrustRootCertSha256Fingerprint() {
177        return mTrustRootCertSha256Fingerprint;
178    }
179
180    /**
181     * Constructor for creating Policy with default values.
182     */
183    public UpdateParameter() {}
184
185    /**
186     * Copy constructor.
187     *
188     * @param source The source to copy from
189     */
190    public UpdateParameter(UpdateParameter source) {
191        if (source == null) {
192            return;
193        }
194        mUpdateIntervalInMinutes = source.mUpdateIntervalInMinutes;
195        mUpdateMethod = source.mUpdateMethod;
196        mRestriction = source.mRestriction;
197        mServerUri = source.mServerUri;
198        mUsername = source.mUsername;
199        mBase64EncodedPassword = source.mBase64EncodedPassword;
200        mTrustRootCertUrl = source.mTrustRootCertUrl;
201        if (source.mTrustRootCertSha256Fingerprint != null) {
202            mTrustRootCertSha256Fingerprint = Arrays.copyOf(source.mTrustRootCertSha256Fingerprint,
203                    source.mTrustRootCertSha256Fingerprint.length);
204        }
205    }
206
207    @Override
208    public int describeContents() {
209        return 0;
210    }
211
212    @Override
213    public void writeToParcel(Parcel dest, int flags) {
214        dest.writeLong(mUpdateIntervalInMinutes);
215        dest.writeString(mUpdateMethod);
216        dest.writeString(mRestriction);
217        dest.writeString(mServerUri);
218        dest.writeString(mUsername);
219        dest.writeString(mBase64EncodedPassword);
220        dest.writeString(mTrustRootCertUrl);
221        dest.writeByteArray(mTrustRootCertSha256Fingerprint);
222    }
223
224    @Override
225    public boolean equals(Object thatObject) {
226        if (this == thatObject) {
227            return true;
228        }
229        if (!(thatObject instanceof UpdateParameter)) {
230            return false;
231        }
232        UpdateParameter that = (UpdateParameter) thatObject;
233
234        return mUpdateIntervalInMinutes == that.mUpdateIntervalInMinutes
235                && TextUtils.equals(mUpdateMethod, that.mUpdateMethod)
236                && TextUtils.equals(mRestriction, that.mRestriction)
237                && TextUtils.equals(mServerUri, that.mServerUri)
238                && TextUtils.equals(mUsername, that.mUsername)
239                && TextUtils.equals(mBase64EncodedPassword, that.mBase64EncodedPassword)
240                && TextUtils.equals(mTrustRootCertUrl, that.mTrustRootCertUrl)
241                && Arrays.equals(mTrustRootCertSha256Fingerprint,
242                        that.mTrustRootCertSha256Fingerprint);
243    }
244
245    @Override
246    public int hashCode() {
247        return Objects.hash(mUpdateIntervalInMinutes, mUpdateMethod, mRestriction, mServerUri,
248                mUsername, mBase64EncodedPassword, mTrustRootCertUrl,
249                mTrustRootCertSha256Fingerprint);
250    }
251
252    @Override
253    public String toString() {
254        StringBuilder builder = new StringBuilder();
255        builder.append("UpdateInterval: ").append(mUpdateIntervalInMinutes).append("\n");
256        builder.append("UpdateMethod: ").append(mUpdateMethod).append("\n");
257        builder.append("Restriction: ").append(mRestriction).append("\n");
258        builder.append("ServerURI: ").append(mServerUri).append("\n");
259        builder.append("Username: ").append(mUsername).append("\n");
260        builder.append("TrustRootCertURL: ").append(mTrustRootCertUrl).append("\n");
261        return builder.toString();
262    }
263
264    /**
265     * Validate UpdateParameter data.
266     *
267     * @return true on success
268     * @hide
269     */
270    public boolean validate() {
271        if (mUpdateIntervalInMinutes == Long.MIN_VALUE) {
272            Log.d(TAG, "Update interval not specified");
273            return false;
274        }
275        // Update not applicable.
276        if (mUpdateIntervalInMinutes == UPDATE_CHECK_INTERVAL_NEVER) {
277            return true;
278        }
279
280        if (!TextUtils.equals(mUpdateMethod, UPDATE_METHOD_OMADM)
281                && !TextUtils.equals(mUpdateMethod, UPDATE_METHOD_SSP)) {
282            Log.d(TAG, "Unknown update method: " + mUpdateMethod);
283            return false;
284        }
285
286        if (!TextUtils.equals(mRestriction, UPDATE_RESTRICTION_HOMESP)
287                && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_ROAMING_PARTNER)
288                && !TextUtils.equals(mRestriction, UPDATE_RESTRICTION_UNRESTRICTED)) {
289            Log.d(TAG, "Unknown restriction: " + mRestriction);
290            return false;
291        }
292
293        if (TextUtils.isEmpty(mServerUri)) {
294            Log.d(TAG, "Missing update server URI");
295            return false;
296        }
297        if (mServerUri.getBytes(StandardCharsets.UTF_8).length > MAX_URI_BYTES) {
298            Log.d(TAG, "URI bytes exceeded the max: "
299                    + mServerUri.getBytes(StandardCharsets.UTF_8).length);
300            return false;
301        }
302
303        if (TextUtils.isEmpty(mUsername)) {
304            Log.d(TAG, "Missing username");
305            return false;
306        }
307        if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
308            Log.d(TAG, "Username bytes exceeded the max: "
309                    + mUsername.getBytes(StandardCharsets.UTF_8).length);
310            return false;
311        }
312
313        if (TextUtils.isEmpty(mBase64EncodedPassword)) {
314            Log.d(TAG, "Missing username");
315            return false;
316        }
317        if (mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
318            Log.d(TAG, "Password bytes exceeded the max: "
319                    + mBase64EncodedPassword.getBytes(StandardCharsets.UTF_8).length);
320            return false;
321        }
322        try {
323            Base64.decode(mBase64EncodedPassword, Base64.DEFAULT);
324        } catch (IllegalArgumentException e) {
325            Log.d(TAG, "Invalid encoding for password: " + mBase64EncodedPassword);
326            return false;
327        }
328
329        if (TextUtils.isEmpty(mTrustRootCertUrl)) {
330            Log.d(TAG, "Missing trust root certificate URL");
331            return false;
332        }
333        if (mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
334            Log.d(TAG, "Trust root cert URL bytes exceeded the max: "
335                    + mTrustRootCertUrl.getBytes(StandardCharsets.UTF_8).length);
336            return false;
337        }
338
339        if (mTrustRootCertSha256Fingerprint == null) {
340            Log.d(TAG, "Missing trust root certificate SHA-256 fingerprint");
341            return false;
342        }
343        if (mTrustRootCertSha256Fingerprint.length != CERTIFICATE_SHA256_BYTES) {
344            Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
345                    + mTrustRootCertSha256Fingerprint.length);
346            return false;
347        }
348        return true;
349    }
350
351    public static final Creator<UpdateParameter> CREATOR =
352        new Creator<UpdateParameter>() {
353            @Override
354            public UpdateParameter createFromParcel(Parcel in) {
355                UpdateParameter updateParam = new UpdateParameter();
356                updateParam.setUpdateIntervalInMinutes(in.readLong());
357                updateParam.setUpdateMethod(in.readString());
358                updateParam.setRestriction(in.readString());
359                updateParam.setServerUri(in.readString());
360                updateParam.setUsername(in.readString());
361                updateParam.setBase64EncodedPassword(in.readString());
362                updateParam.setTrustRootCertUrl(in.readString());
363                updateParam.setTrustRootCertSha256Fingerprint(in.createByteArray());
364                return updateParam;
365            }
366
367            @Override
368            public UpdateParameter[] newArray(int size) {
369                return new UpdateParameter[size];
370            }
371        };
372}
373