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