1 /**
2  * Copyright (c) 2016, 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;
18 
19 import android.net.wifi.hotspot2.pps.Credential;
20 import android.net.wifi.hotspot2.pps.HomeSp;
21 import android.net.wifi.hotspot2.pps.Policy;
22 import android.net.wifi.hotspot2.pps.UpdateParameter;
23 import android.os.Parcelable;
24 import android.text.TextUtils;
25 import android.util.Log;
26 import android.os.Parcel;
27 
28 import java.nio.charset.StandardCharsets;
29 import java.util.Arrays;
30 import java.util.Collections;
31 import java.util.Date;
32 import java.util.HashMap;
33 import java.util.Map;
34 import java.util.Objects;
35 
36 /**
37  * Class representing Passpoint configuration.  This contains configurations specified in
38  * PerProviderSubscription (PPS) Management Object (MO) tree.
39  *
40  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
41  * Release 2 Technical Specification.
42  */
43 public final class PasspointConfiguration implements Parcelable {
44     private static final String TAG = "PasspointConfiguration";
45 
46     /**
47      * Number of bytes for certificate SHA-256 fingerprint byte array.
48      */
49     private static final int CERTIFICATE_SHA256_BYTES = 32;
50 
51     /**
52      * Maximum bytes for URL string.
53      */
54     private static final int MAX_URL_BYTES = 1023;
55 
56     /**
57      * Integer value used for indicating null value in the Parcel.
58      */
59     private static final int NULL_VALUE = -1;
60 
61     /**
62      * Configurations under HomeSp subtree.
63      */
64     private HomeSp mHomeSp = null;
65     /**
66      * Set the Home SP (Service Provider) information.
67      *
68      * @param homeSp The Home SP information to set to
69      */
setHomeSp(HomeSp homeSp)70     public void setHomeSp(HomeSp homeSp) { mHomeSp = homeSp; }
71     /**
72      * Get the Home SP (Service Provider) information.
73      *
74      * @return Home SP information
75      */
getHomeSp()76     public HomeSp getHomeSp() { return mHomeSp; }
77 
78     /**
79      * Configurations under Credential subtree.
80      */
81     private Credential mCredential = null;
82     /**
83      * Set the credential information.
84      *
85      * @param credential The credential information to set to
86      */
setCredential(Credential credential)87     public void setCredential(Credential credential) {
88         mCredential = credential;
89     }
90     /**
91      * Get the credential information.
92      *
93      * @return credential information
94      */
getCredential()95     public Credential getCredential() {
96         return mCredential;
97     }
98 
99     /**
100      * Configurations under Policy subtree.
101      */
102     private Policy mPolicy = null;
103     /**
104      * @hide
105      */
setPolicy(Policy policy)106     public void setPolicy(Policy policy) {
107         mPolicy = policy;
108     }
109     /**
110      * @hide
111      */
getPolicy()112     public Policy getPolicy() {
113         return mPolicy;
114     }
115 
116     /**
117      * Meta data for performing subscription update.
118      */
119     private UpdateParameter mSubscriptionUpdate = null;
120     /**
121      * @hide
122      */
setSubscriptionUpdate(UpdateParameter subscriptionUpdate)123     public void setSubscriptionUpdate(UpdateParameter subscriptionUpdate) {
124         mSubscriptionUpdate = subscriptionUpdate;
125     }
126     /**
127      * @hide
128      */
getSubscriptionUpdate()129     public UpdateParameter getSubscriptionUpdate() {
130         return mSubscriptionUpdate;
131     }
132 
133     /**
134      * List of HTTPS URL for retrieving trust root certificate and the corresponding SHA-256
135      * fingerprint of the certificate.  The certificates are used for verifying AAA server's
136      * identity during EAP authentication.
137      */
138     private Map<String, byte[]> mTrustRootCertList = null;
139     /**
140      * @hide
141      */
setTrustRootCertList(Map<String, byte[]> trustRootCertList)142     public void setTrustRootCertList(Map<String, byte[]> trustRootCertList) {
143         mTrustRootCertList = trustRootCertList;
144     }
145     /**
146      * @hide
147      */
getTrustRootCertList()148     public Map<String, byte[]> getTrustRootCertList() {
149         return mTrustRootCertList;
150     }
151 
152     /**
153      * Set by the subscription server, updated every time the configuration is updated by
154      * the subscription server.
155      *
156      * Use Integer.MIN_VALUE to indicate unset value.
157      */
158     private int mUpdateIdentifier = Integer.MIN_VALUE;
159     /**
160      * @hide
161      */
setUpdateIdentifier(int updateIdentifier)162     public void setUpdateIdentifier(int updateIdentifier) {
163         mUpdateIdentifier = updateIdentifier;
164     }
165     /**
166      * @hide
167      */
getUpdateIdentifier()168     public int getUpdateIdentifier() {
169         return mUpdateIdentifier;
170     }
171 
172     /**
173      * The priority of the credential.
174      *
175      * Use Integer.MIN_VALUE to indicate unset value.
176      */
177     private int mCredentialPriority = Integer.MIN_VALUE;
178     /**
179      * @hide
180      */
setCredentialPriority(int credentialPriority)181     public void setCredentialPriority(int credentialPriority) {
182         mCredentialPriority = credentialPriority;
183     }
184     /**
185      * @hide
186      */
getCredentialPriority()187     public int getCredentialPriority() {
188         return mCredentialPriority;
189     }
190 
191     /**
192      * The time this subscription is created. It is in the format of number
193      * of milliseconds since January 1, 1970, 00:00:00 GMT.
194      *
195      * Use Long.MIN_VALUE to indicate unset value.
196      */
197     private long mSubscriptionCreationTimeInMillis = Long.MIN_VALUE;
198     /**
199      * @hide
200      */
setSubscriptionCreationTimeInMillis(long subscriptionCreationTimeInMillis)201     public void setSubscriptionCreationTimeInMillis(long subscriptionCreationTimeInMillis) {
202         mSubscriptionCreationTimeInMillis = subscriptionCreationTimeInMillis;
203     }
204     /**
205      * @hide
206      */
getSubscriptionCreationTimeInMillis()207     public long getSubscriptionCreationTimeInMillis() {
208         return mSubscriptionCreationTimeInMillis;
209     }
210 
211     /**
212      * The time this subscription will expire. It is in the format of number
213      * of milliseconds since January 1, 1970, 00:00:00 GMT.
214      *
215      * Use Long.MIN_VALUE to indicate unset value.
216      */
217     private long mSubscriptionExpirationTimeInMillis = Long.MIN_VALUE;
218     /**
219      * @hide
220      */
setSubscriptionExpirationTimeInMillis(long subscriptionExpirationTimeInMillis)221     public void setSubscriptionExpirationTimeInMillis(long subscriptionExpirationTimeInMillis) {
222         mSubscriptionExpirationTimeInMillis = subscriptionExpirationTimeInMillis;
223     }
224     /**
225      * @hide
226      */
getSubscriptionExpirationTimeInMillis()227     public long getSubscriptionExpirationTimeInMillis() {
228         return mSubscriptionExpirationTimeInMillis;
229     }
230 
231     /**
232      * The type of the subscription.  This is defined by the provider and the value is provider
233      * specific.
234      */
235     private String mSubscriptionType = null;
236     /**
237      * @hide
238      */
setSubscriptionType(String subscriptionType)239     public void setSubscriptionType(String subscriptionType) {
240         mSubscriptionType = subscriptionType;
241     }
242     /**
243      * @hide
244      */
getSubscriptionType()245     public String getSubscriptionType() {
246         return mSubscriptionType;
247     }
248 
249     /**
250      * The time period for usage statistics accumulation. A value of zero means that usage
251      * statistics are not accumulated on a periodic basis (e.g., a one-time limit for
252      * “pay as you go” - PAYG service). A non-zero value specifies the usage interval in minutes.
253      */
254     private long mUsageLimitUsageTimePeriodInMinutes = Long.MIN_VALUE;
255     /**
256      * @hide
257      */
setUsageLimitUsageTimePeriodInMinutes(long usageLimitUsageTimePeriodInMinutes)258     public void setUsageLimitUsageTimePeriodInMinutes(long usageLimitUsageTimePeriodInMinutes) {
259         mUsageLimitUsageTimePeriodInMinutes = usageLimitUsageTimePeriodInMinutes;
260     }
261     /**
262      * @hide
263      */
getUsageLimitUsageTimePeriodInMinutes()264     public long getUsageLimitUsageTimePeriodInMinutes() {
265         return mUsageLimitUsageTimePeriodInMinutes;
266     }
267 
268     /**
269      * The time at which usage statistic accumulation  begins.  It is in the format of number
270      * of milliseconds since January 1, 1970, 00:00:00 GMT.
271      *
272      * Use Long.MIN_VALUE to indicate unset value.
273      */
274     private long mUsageLimitStartTimeInMillis = Long.MIN_VALUE;
275     /**
276      * @hide
277      */
setUsageLimitStartTimeInMillis(long usageLimitStartTimeInMillis)278     public void setUsageLimitStartTimeInMillis(long usageLimitStartTimeInMillis) {
279         mUsageLimitStartTimeInMillis = usageLimitStartTimeInMillis;
280     }
281     /**
282      * @hide
283      */
getUsageLimitStartTimeInMillis()284     public long getUsageLimitStartTimeInMillis() {
285         return mUsageLimitStartTimeInMillis;
286     }
287 
288     /**
289      * The cumulative data limit in megabytes for the {@link #usageLimitUsageTimePeriodInMinutes}.
290      * A value of zero indicate unlimited data usage.
291      *
292      * Use Long.MIN_VALUE to indicate unset value.
293      */
294     private long mUsageLimitDataLimit = Long.MIN_VALUE;
295     /**
296      * @hide
297      */
setUsageLimitDataLimit(long usageLimitDataLimit)298     public void setUsageLimitDataLimit(long usageLimitDataLimit) {
299         mUsageLimitDataLimit = usageLimitDataLimit;
300     }
301     /**
302      * @hide
303      */
getUsageLimitDataLimit()304     public long getUsageLimitDataLimit() {
305         return mUsageLimitDataLimit;
306     }
307 
308     /**
309      * The cumulative time limit in minutes for the {@link #usageLimitUsageTimePeriodInMinutes}.
310      * A value of zero indicate unlimited time usage.
311      */
312     private long mUsageLimitTimeLimitInMinutes = Long.MIN_VALUE;
313     /**
314      * @hide
315      */
setUsageLimitTimeLimitInMinutes(long usageLimitTimeLimitInMinutes)316     public void setUsageLimitTimeLimitInMinutes(long usageLimitTimeLimitInMinutes) {
317         mUsageLimitTimeLimitInMinutes = usageLimitTimeLimitInMinutes;
318     }
319     /**
320      * @hide
321      */
getUsageLimitTimeLimitInMinutes()322     public long getUsageLimitTimeLimitInMinutes() {
323         return mUsageLimitTimeLimitInMinutes;
324     }
325 
326     /**
327      * Constructor for creating PasspointConfiguration with default values.
328      */
PasspointConfiguration()329     public PasspointConfiguration() {}
330 
331     /**
332      * Copy constructor.
333      *
334      * @param source The source to copy from
335      */
PasspointConfiguration(PasspointConfiguration source)336     public PasspointConfiguration(PasspointConfiguration source) {
337         if (source == null) {
338             return;
339         }
340 
341         if (source.mHomeSp != null) {
342             mHomeSp = new HomeSp(source.mHomeSp);
343         }
344         if (source.mCredential != null) {
345             mCredential = new Credential(source.mCredential);
346         }
347         if (source.mPolicy != null) {
348             mPolicy = new Policy(source.mPolicy);
349         }
350         if (source.mTrustRootCertList != null) {
351             mTrustRootCertList = Collections.unmodifiableMap(source.mTrustRootCertList);
352         }
353         if (source.mSubscriptionUpdate != null) {
354             mSubscriptionUpdate = new UpdateParameter(source.mSubscriptionUpdate);
355         }
356         mUpdateIdentifier = source.mUpdateIdentifier;
357         mCredentialPriority = source.mCredentialPriority;
358         mSubscriptionCreationTimeInMillis = source.mSubscriptionCreationTimeInMillis;
359         mSubscriptionExpirationTimeInMillis = source.mSubscriptionExpirationTimeInMillis;
360         mSubscriptionType = source.mSubscriptionType;
361         mUsageLimitDataLimit = source.mUsageLimitDataLimit;
362         mUsageLimitStartTimeInMillis = source.mUsageLimitStartTimeInMillis;
363         mUsageLimitTimeLimitInMinutes = source.mUsageLimitTimeLimitInMinutes;
364         mUsageLimitUsageTimePeriodInMinutes = source.mUsageLimitUsageTimePeriodInMinutes;
365     }
366 
367     @Override
describeContents()368     public int describeContents() {
369         return 0;
370     }
371 
372     @Override
writeToParcel(Parcel dest, int flags)373     public void writeToParcel(Parcel dest, int flags) {
374         dest.writeParcelable(mHomeSp, flags);
375         dest.writeParcelable(mCredential, flags);
376         dest.writeParcelable(mPolicy, flags);
377         dest.writeParcelable(mSubscriptionUpdate, flags);
378         writeTrustRootCerts(dest, mTrustRootCertList);
379         dest.writeInt(mUpdateIdentifier);
380         dest.writeInt(mCredentialPriority);
381         dest.writeLong(mSubscriptionCreationTimeInMillis);
382         dest.writeLong(mSubscriptionExpirationTimeInMillis);
383         dest.writeString(mSubscriptionType);
384         dest.writeLong(mUsageLimitUsageTimePeriodInMinutes);
385         dest.writeLong(mUsageLimitStartTimeInMillis);
386         dest.writeLong(mUsageLimitDataLimit);
387         dest.writeLong(mUsageLimitTimeLimitInMinutes);
388     }
389 
390     @Override
equals(Object thatObject)391     public boolean equals(Object thatObject) {
392         if (this == thatObject) {
393             return true;
394         }
395         if (!(thatObject instanceof PasspointConfiguration)) {
396             return false;
397         }
398         PasspointConfiguration that = (PasspointConfiguration) thatObject;
399         return (mHomeSp == null ? that.mHomeSp == null : mHomeSp.equals(that.mHomeSp))
400                 && (mCredential == null ? that.mCredential == null
401                         : mCredential.equals(that.mCredential))
402                 && (mPolicy == null ? that.mPolicy == null : mPolicy.equals(that.mPolicy))
403                 && (mSubscriptionUpdate == null ? that.mSubscriptionUpdate == null
404                         : mSubscriptionUpdate.equals(that.mSubscriptionUpdate))
405                 && isTrustRootCertListEquals(mTrustRootCertList, that.mTrustRootCertList)
406                 && mUpdateIdentifier == that.mUpdateIdentifier
407                 && mCredentialPriority == that.mCredentialPriority
408                 && mSubscriptionCreationTimeInMillis == that.mSubscriptionCreationTimeInMillis
409                 && mSubscriptionExpirationTimeInMillis == that.mSubscriptionExpirationTimeInMillis
410                 && TextUtils.equals(mSubscriptionType, that.mSubscriptionType)
411                 && mUsageLimitUsageTimePeriodInMinutes == that.mUsageLimitUsageTimePeriodInMinutes
412                 && mUsageLimitStartTimeInMillis == that.mUsageLimitStartTimeInMillis
413                 && mUsageLimitDataLimit == that.mUsageLimitDataLimit
414                 && mUsageLimitTimeLimitInMinutes == that.mUsageLimitTimeLimitInMinutes;
415     }
416 
417     @Override
hashCode()418     public int hashCode() {
419         return Objects.hash(mHomeSp, mCredential, mPolicy, mSubscriptionUpdate, mTrustRootCertList,
420                 mUpdateIdentifier, mCredentialPriority, mSubscriptionCreationTimeInMillis,
421                 mSubscriptionExpirationTimeInMillis, mUsageLimitUsageTimePeriodInMinutes,
422                 mUsageLimitStartTimeInMillis, mUsageLimitDataLimit, mUsageLimitTimeLimitInMinutes);
423     }
424 
425     @Override
toString()426     public String toString() {
427         StringBuilder builder = new StringBuilder();
428         builder.append("UpdateIdentifier: ").append(mUpdateIdentifier).append("\n");
429         builder.append("CredentialPriority: ").append(mCredentialPriority).append("\n");
430         builder.append("SubscriptionCreationTime: ").append(
431                 mSubscriptionCreationTimeInMillis != Long.MIN_VALUE
432                 ? new Date(mSubscriptionCreationTimeInMillis) : "Not specified").append("\n");
433         builder.append("SubscriptionExpirationTime: ").append(
434                 mSubscriptionExpirationTimeInMillis != Long.MIN_VALUE
435                 ? new Date(mSubscriptionExpirationTimeInMillis) : "Not specified").append("\n");
436         builder.append("UsageLimitStartTime: ").append(mUsageLimitStartTimeInMillis != Long.MIN_VALUE
437                 ? new Date(mUsageLimitStartTimeInMillis) : "Not specified").append("\n");
438         builder.append("UsageTimePeriod: ").append(mUsageLimitUsageTimePeriodInMinutes)
439                 .append("\n");
440         builder.append("UsageLimitDataLimit: ").append(mUsageLimitDataLimit).append("\n");
441         builder.append("UsageLimitTimeLimit: ").append(mUsageLimitTimeLimitInMinutes).append("\n");
442         if (mHomeSp != null) {
443             builder.append("HomeSP Begin ---\n");
444             builder.append(mHomeSp);
445             builder.append("HomeSP End ---\n");
446         }
447         if (mCredential != null) {
448             builder.append("Credential Begin ---\n");
449             builder.append(mCredential);
450             builder.append("Credential End ---\n");
451         }
452         if (mPolicy != null) {
453             builder.append("Policy Begin ---\n");
454             builder.append(mPolicy);
455             builder.append("Policy End ---\n");
456         }
457         if (mSubscriptionUpdate != null) {
458             builder.append("SubscriptionUpdate Begin ---\n");
459             builder.append(mSubscriptionUpdate);
460             builder.append("SubscriptionUpdate End ---\n");
461         }
462         if (mTrustRootCertList != null) {
463             builder.append("TrustRootCertServers: ").append(mTrustRootCertList.keySet())
464                     .append("\n");
465         }
466         return builder.toString();
467     }
468 
469     /**
470      * Validate the configuration data.
471      *
472      * @return true on success or false on failure
473      * @hide
474      */
validate()475     public boolean validate() {
476         if (mHomeSp == null || !mHomeSp.validate()) {
477             return false;
478         }
479         if (mCredential == null || !mCredential.validate()) {
480             return false;
481         }
482         if (mPolicy != null && !mPolicy.validate()) {
483             return false;
484         }
485         if (mSubscriptionUpdate != null && !mSubscriptionUpdate.validate()) {
486             return false;
487         }
488         if (mTrustRootCertList != null) {
489             for (Map.Entry<String, byte[]> entry : mTrustRootCertList.entrySet()) {
490                 String url = entry.getKey();
491                 byte[] certFingerprint = entry.getValue();
492                 if (TextUtils.isEmpty(url)) {
493                     Log.d(TAG, "Empty URL");
494                     return false;
495                 }
496                 if (url.getBytes(StandardCharsets.UTF_8).length > MAX_URL_BYTES) {
497                     Log.d(TAG, "URL bytes exceeded the max: "
498                             + url.getBytes(StandardCharsets.UTF_8).length);
499                     return false;
500                 }
501 
502                 if (certFingerprint == null) {
503                     Log.d(TAG, "Fingerprint not specified");
504                     return false;
505                 }
506                 if (certFingerprint.length != CERTIFICATE_SHA256_BYTES) {
507                     Log.d(TAG, "Incorrect size of trust root certificate SHA-256 fingerprint: "
508                             + certFingerprint.length);
509                     return false;
510                 }
511             }
512         }
513         return true;
514     }
515 
516     public static final Creator<PasspointConfiguration> CREATOR =
517         new Creator<PasspointConfiguration>() {
518             @Override
519             public PasspointConfiguration createFromParcel(Parcel in) {
520                 PasspointConfiguration config = new PasspointConfiguration();
521                 config.setHomeSp(in.readParcelable(null));
522                 config.setCredential(in.readParcelable(null));
523                 config.setPolicy(in.readParcelable(null));
524                 config.setSubscriptionUpdate(in.readParcelable(null));
525                 config.setTrustRootCertList(readTrustRootCerts(in));
526                 config.setUpdateIdentifier(in.readInt());
527                 config.setCredentialPriority(in.readInt());
528                 config.setSubscriptionCreationTimeInMillis(in.readLong());
529                 config.setSubscriptionExpirationTimeInMillis(in.readLong());
530                 config.setSubscriptionType(in.readString());
531                 config.setUsageLimitUsageTimePeriodInMinutes(in.readLong());
532                 config.setUsageLimitStartTimeInMillis(in.readLong());
533                 config.setUsageLimitDataLimit(in.readLong());
534                 config.setUsageLimitTimeLimitInMinutes(in.readLong());
535                 return config;
536             }
537 
538             @Override
539             public PasspointConfiguration[] newArray(int size) {
540                 return new PasspointConfiguration[size];
541             }
542 
543             /**
544              * Helper function for reading trust root certificate info list from a Parcel.
545              *
546              * @param in The Parcel to read from
547              * @return The list of trust root certificate URL with the corresponding certificate
548              *         fingerprint
549              */
550             private Map<String, byte[]> readTrustRootCerts(Parcel in) {
551                 int size = in.readInt();
552                 if (size == NULL_VALUE) {
553                     return null;
554                 }
555                 Map<String, byte[]> trustRootCerts = new HashMap<>(size);
556                 for (int i = 0; i < size; i++) {
557                     String key = in.readString();
558                     byte[] value = in.createByteArray();
559                     trustRootCerts.put(key, value);
560                 }
561                 return trustRootCerts;
562             }
563         };
564 
565     /**
566      * Helper function for writing trust root certificate information list.
567      *
568      * @param dest The Parcel to write to
569      * @param trustRootCerts The list of trust root certificate URL with the corresponding
570      *                       certificate fingerprint
571      */
writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts)572     private static void writeTrustRootCerts(Parcel dest, Map<String, byte[]> trustRootCerts) {
573         if (trustRootCerts == null) {
574             dest.writeInt(NULL_VALUE);
575             return;
576         }
577         dest.writeInt(trustRootCerts.size());
578         for (Map.Entry<String, byte[]> entry : trustRootCerts.entrySet()) {
579             dest.writeString(entry.getKey());
580             dest.writeByteArray(entry.getValue());
581         }
582     }
583 
584     /**
585      * Helper function for comparing two trust root certificate list.  Cannot use Map#equals
586      * method since the value type (byte[]) doesn't override equals method.
587      *
588      * @param list1 The first trust root certificate list
589      * @param list2 The second trust root certificate list
590      * @return true if the two list are equal
591      */
isTrustRootCertListEquals(Map<String, byte[]> list1, Map<String, byte[]> list2)592     private static boolean isTrustRootCertListEquals(Map<String, byte[]> list1,
593             Map<String, byte[]> list2) {
594         if (list1 == null || list2 == null) {
595             return list1 == list2;
596         }
597         if (list1.size() != list2.size()) {
598             return false;
599         }
600         for (Map.Entry<String, byte[]> entry : list1.entrySet()) {
601             if (!Arrays.equals(entry.getValue(), list2.get(entry.getKey()))) {
602                 return false;
603             }
604         }
605         return true;
606     }
607 }
608