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.pps;
18 
19 import static android.net.wifi.hotspot2.PasspointConfiguration.MAX_STRING_LENGTH;
20 
21 import android.net.wifi.EAPConstants;
22 import android.net.wifi.ParcelUtil;
23 import android.net.wifi.WifiEnterpriseConfig;
24 import android.os.Parcel;
25 import android.os.Parcelable;
26 import android.text.TextUtils;
27 import android.util.Log;
28 
29 import java.nio.charset.StandardCharsets;
30 import java.security.MessageDigest;
31 import java.security.NoSuchAlgorithmException;
32 import java.security.PrivateKey;
33 import java.security.cert.CertificateEncodingException;
34 import java.security.cert.X509Certificate;
35 import java.util.Arrays;
36 import java.util.Date;
37 import java.util.HashSet;
38 import java.util.Objects;
39 import java.util.Set;
40 
41 /**
42  * Class representing Credential subtree in the PerProviderSubscription (PPS)
43  * Management Object (MO) tree.
44  * For more info, refer to Hotspot 2.0 PPS MO defined in section 9.1 of the Hotspot 2.0
45  * Release 2 Technical Specification.
46  *
47  * In addition to the fields in the Credential subtree, this will also maintain necessary
48  * information for the private key and certificates associated with this credential.
49  */
50 public final class Credential implements Parcelable {
51     private static final String TAG = "Credential";
52 
53     /**
54      * Max string length for realm.  Refer to Credential/Realm node in Hotspot 2.0 Release 2
55      * Technical Specification Section 9.1 for more info.
56      */
57     private static final int MAX_REALM_BYTES = 253;
58 
59     /**
60      * The time this credential is created. It is in the format of number
61      * of milliseconds since January 1, 1970, 00:00:00 GMT.
62      * Using Long.MIN_VALUE to indicate unset value.
63      */
64     private long mCreationTimeInMillis = Long.MIN_VALUE;
65     /**
66      * @hide
67      */
setCreationTimeInMillis(long creationTimeInMillis)68     public void setCreationTimeInMillis(long creationTimeInMillis) {
69         mCreationTimeInMillis = creationTimeInMillis;
70     }
71     /**
72      * @hide
73      */
getCreationTimeInMillis()74     public long getCreationTimeInMillis() {
75         return mCreationTimeInMillis;
76     }
77 
78     /**
79      * The time this credential will expire. It is in the format of number
80      * of milliseconds since January 1, 1970, 00:00:00 GMT.
81     * Using Long.MIN_VALUE to indicate unset value.
82      */
83     private long mExpirationTimeInMillis = Long.MIN_VALUE;
84     /**
85      * @hide
86      */
setExpirationTimeInMillis(long expirationTimeInMillis)87     public void setExpirationTimeInMillis(long expirationTimeInMillis) {
88         mExpirationTimeInMillis = expirationTimeInMillis;
89     }
90     /**
91      * @hide
92      */
getExpirationTimeInMillis()93     public long getExpirationTimeInMillis() {
94         return mExpirationTimeInMillis;
95     }
96 
97     /**
98      * The realm associated with this credential.  It will be used to determine
99      * if this credential can be used to authenticate with a given hotspot by
100      * comparing the realm specified in that hotspot's ANQP element.
101      */
102     private String mRealm = null;
103     /**
104      * Set the realm associated with this credential.
105      *
106      * @param realm The realm to set to
107      */
setRealm(String realm)108     public void setRealm(String realm) {
109         mRealm = realm;
110     }
111     /**
112      * Get the realm associated with this credential.
113      *
114      * @return the realm associated with this credential
115      */
getRealm()116     public String getRealm() {
117         return mRealm;
118     }
119 
120     /**
121      * When set to true, the device should check AAA (Authentication, Authorization,
122      * and Accounting) server's certificate during EAP (Extensible Authentication
123      * Protocol) authentication.
124      */
125     private boolean mCheckAaaServerCertStatus = false;
126     /**
127      * @hide
128      */
setCheckAaaServerCertStatus(boolean checkAaaServerCertStatus)129     public void setCheckAaaServerCertStatus(boolean checkAaaServerCertStatus) {
130         mCheckAaaServerCertStatus = checkAaaServerCertStatus;
131     }
132     /**
133      * @hide
134      */
getCheckAaaServerCertStatus()135     public boolean getCheckAaaServerCertStatus() {
136         return mCheckAaaServerCertStatus;
137     }
138 
139     /**
140      * Username-password based credential.
141      * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
142      */
143     public static final class UserCredential implements Parcelable {
144         /**
145          * Maximum string length for username.  Refer to Credential/UsernamePassword/Username
146          * node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
147          */
148         private static final int MAX_USERNAME_BYTES = 63;
149 
150         /**
151          * Maximum string length for password.  Refer to Credential/UsernamePassword/Password
152          * in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
153          */
154         private static final int MAX_PASSWORD_BYTES = 255;
155 
156         /**
157          * Supported authentication methods.
158          * @hide
159          */
160         public static final String AUTH_METHOD_PAP = "PAP";
161         /** @hide */
162         public static final String AUTH_METHOD_MSCHAP = "MS-CHAP";
163         /** @hide */
164         public static final String AUTH_METHOD_MSCHAPV2 = "MS-CHAP-V2";
165 
166         /**
167          * Supported Non-EAP inner methods.  Refer to
168          * Credential/UsernamePassword/EAPMethod/InnerEAPType in Hotspot 2.0 Release 2 Technical
169          * Specification Section 9.1 for more info.
170          */
171         private static final Set<String> SUPPORTED_AUTH = new HashSet<String>(
172                 Arrays.asList(AUTH_METHOD_PAP, AUTH_METHOD_MSCHAP, AUTH_METHOD_MSCHAPV2));
173 
174         /**
175          * Username of the credential.
176          */
177         private String mUsername = null;
178         /**
179          * Set the username associated with this user credential.
180          *
181          * @param username The username to set to
182          */
setUsername(String username)183         public void setUsername(String username) {
184             mUsername = username;
185         }
186         /**
187          * Get the username associated with this user credential.
188          *
189          * @return the username associated with this user credential
190          */
getUsername()191         public String getUsername() {
192             return mUsername;
193         }
194 
195         /**
196          * Base64-encoded password.
197          */
198         private String mPassword = null;
199         /**
200          * Set the Base64-encoded password associated with this user credential.
201          *
202          * @param password The password to set to
203          */
setPassword(String password)204         public void setPassword(String password) {
205             mPassword = password;
206         }
207         /**
208          * Get the Base64-encoded password associated with this user credential.
209          *
210          * @return the Base64-encoded password associated with this user credential
211          */
getPassword()212         public String getPassword() {
213             return mPassword;
214         }
215 
216         /**
217          * Flag indicating if the password is machine managed.
218          */
219         private boolean mMachineManaged = false;
220         /**
221          * @hide
222          */
setMachineManaged(boolean machineManaged)223         public void setMachineManaged(boolean machineManaged) {
224             mMachineManaged = machineManaged;
225         }
226         /**
227          * @hide
228          */
getMachineManaged()229         public boolean getMachineManaged() {
230             return mMachineManaged;
231         }
232 
233         /**
234          * The name of the application used to generate the password.
235          */
236         private String mSoftTokenApp = null;
237         /**
238          * @hide
239          */
setSoftTokenApp(String softTokenApp)240         public void setSoftTokenApp(String softTokenApp) {
241             mSoftTokenApp = softTokenApp;
242         }
243         /**
244          * @hide
245          */
getSoftTokenApp()246         public String getSoftTokenApp() {
247             return mSoftTokenApp;
248         }
249 
250         /**
251          * Flag indicating if this credential is usable on other mobile devices as well.
252          */
253         private boolean mAbleToShare = false;
254         /**
255          * @hide
256          */
setAbleToShare(boolean ableToShare)257         public void setAbleToShare(boolean ableToShare) {
258             mAbleToShare = ableToShare;
259         }
260         /**
261          * @hide
262          */
getAbleToShare()263         public boolean getAbleToShare() {
264             return mAbleToShare;
265         }
266 
267         /**
268          * EAP (Extensible Authentication Protocol) method type.
269          * Refer to
270          * <a href="http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4">
271          * EAP Numbers</a> for valid values.
272          * Using Integer.MIN_VALUE to indicate unset value.
273          */
274         private int mEapType = Integer.MIN_VALUE;
275         /**
276          * Set the EAP (Extensible Authentication Protocol) method type associated with this
277          * user credential.
278          * Refer to
279          * <a href="http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4">
280          * EAP Numbers</a> for valid values.
281          *
282          * @param eapType The EAP method type associated with this user credential
283          */
setEapType(int eapType)284         public void setEapType(int eapType) {
285             mEapType = eapType;
286         }
287         /**
288          * Get the EAP (Extensible Authentication Protocol) method type associated with this
289          * user credential.
290          *
291          * @return EAP method type
292          */
getEapType()293         public int getEapType() {
294             return mEapType;
295         }
296 
297         /**
298          * Non-EAP inner authentication method.
299          */
300         private String mNonEapInnerMethod = null;
301         /**
302          * Set the inner non-EAP method associated with this user credential.
303          *
304          * @param nonEapInnerMethod The non-EAP inner method to set to
305          */
setNonEapInnerMethod(String nonEapInnerMethod)306         public void setNonEapInnerMethod(String nonEapInnerMethod) {
307             mNonEapInnerMethod = nonEapInnerMethod;
308         }
309         /**
310          * Get the inner non-EAP method associated with this user credential.
311          *
312          * @return Non-EAP inner method associated with this user credential
313          */
getNonEapInnerMethod()314         public String getNonEapInnerMethod() {
315             return mNonEapInnerMethod;
316         }
317 
318         /**
319          * Constructor for creating UserCredential with default values.
320          */
UserCredential()321         public UserCredential() {}
322 
323         /**
324          * Copy constructor.
325          *
326          * @param source The source to copy from
327          */
UserCredential(UserCredential source)328         public UserCredential(UserCredential source) {
329             if (source != null) {
330                 mUsername = source.mUsername;
331                 mPassword = source.mPassword;
332                 mMachineManaged = source.mMachineManaged;
333                 mSoftTokenApp = source.mSoftTokenApp;
334                 mAbleToShare = source.mAbleToShare;
335                 mEapType = source.mEapType;
336                 mNonEapInnerMethod = source.mNonEapInnerMethod;
337             }
338         }
339 
340         @Override
describeContents()341         public int describeContents() {
342             return 0;
343         }
344 
345         @Override
writeToParcel(Parcel dest, int flags)346         public void writeToParcel(Parcel dest, int flags) {
347             dest.writeString(mUsername);
348             dest.writeString(mPassword);
349             dest.writeInt(mMachineManaged ? 1 : 0);
350             dest.writeString(mSoftTokenApp);
351             dest.writeInt(mAbleToShare ? 1 : 0);
352             dest.writeInt(mEapType);
353             dest.writeString(mNonEapInnerMethod);
354         }
355 
356         @Override
equals(Object thatObject)357         public boolean equals(Object thatObject) {
358             if (this == thatObject) {
359                 return true;
360             }
361             if (!(thatObject instanceof UserCredential)) {
362                 return false;
363             }
364 
365             UserCredential that = (UserCredential) thatObject;
366             return TextUtils.equals(mUsername, that.mUsername)
367                     && TextUtils.equals(mPassword, that.mPassword)
368                     && mMachineManaged == that.mMachineManaged
369                     && TextUtils.equals(mSoftTokenApp, that.mSoftTokenApp)
370                     && mAbleToShare == that.mAbleToShare
371                     && mEapType == that.mEapType
372                     && TextUtils.equals(mNonEapInnerMethod, that.mNonEapInnerMethod);
373         }
374 
375         @Override
hashCode()376         public int hashCode() {
377             return Objects.hash(mUsername, mPassword, mMachineManaged, mSoftTokenApp,
378                     mAbleToShare, mEapType, mNonEapInnerMethod);
379         }
380 
381         @Override
toString()382         public String toString() {
383             StringBuilder builder = new StringBuilder();
384             builder.append("Username: ").append(mUsername).append("\n");
385             builder.append("MachineManaged: ").append(mMachineManaged).append("\n");
386             builder.append("SoftTokenApp: ").append(mSoftTokenApp).append("\n");
387             builder.append("AbleToShare: ").append(mAbleToShare).append("\n");
388             builder.append("EAPType: ").append(mEapType).append("\n");
389             builder.append("AuthMethod: ").append(mNonEapInnerMethod).append("\n");
390             return builder.toString();
391         }
392 
393         /**
394          * Validate the configuration data.
395          *
396          * @return true on success or false on failure
397          * @hide
398          */
validate()399         public boolean validate() {
400             if (TextUtils.isEmpty(mUsername)) {
401                 Log.d(TAG, "Missing username");
402                 return false;
403             }
404             if (mUsername.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
405                 Log.d(TAG, "username exceeding maximum length: "
406                         + mUsername.getBytes(StandardCharsets.UTF_8).length);
407                 return false;
408             }
409 
410             if (TextUtils.isEmpty(mPassword)) {
411                 Log.d(TAG, "Missing password");
412                 return false;
413             }
414             if (mPassword.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
415                 Log.d(TAG, "password exceeding maximum length: "
416                         + mPassword.getBytes(StandardCharsets.UTF_8).length);
417                 return false;
418             }
419             if (mSoftTokenApp != null) {
420                 if (mSoftTokenApp.getBytes(StandardCharsets.UTF_8).length > MAX_STRING_LENGTH) {
421                     Log.d(TAG, "app name exceeding maximum length: "
422                             + mSoftTokenApp.getBytes(StandardCharsets.UTF_8).length);
423                     return false;
424                 }
425             }
426 
427             // Only supports EAP-TTLS for user credential.
428             if (mEapType != EAPConstants.EAP_TTLS) {
429                 Log.d(TAG, "Invalid EAP Type for user credential: " + mEapType);
430                 return false;
431             }
432 
433             // Verify Non-EAP inner method for EAP-TTLS.
434             if (!SUPPORTED_AUTH.contains(mNonEapInnerMethod)) {
435                 Log.d(TAG, "Invalid non-EAP inner method for EAP-TTLS: " + mNonEapInnerMethod);
436                 return false;
437             }
438             return true;
439         }
440 
441         public static final @android.annotation.NonNull Creator<UserCredential> CREATOR =
442             new Creator<UserCredential>() {
443                 @Override
444                 public UserCredential createFromParcel(Parcel in) {
445                     UserCredential userCredential = new UserCredential();
446                     userCredential.setUsername(in.readString());
447                     userCredential.setPassword(in.readString());
448                     userCredential.setMachineManaged(in.readInt() != 0);
449                     userCredential.setSoftTokenApp(in.readString());
450                     userCredential.setAbleToShare(in.readInt() != 0);
451                     userCredential.setEapType(in.readInt());
452                     userCredential.setNonEapInnerMethod(in.readString());
453                     return userCredential;
454                 }
455 
456                 @Override
457                 public UserCredential[] newArray(int size) {
458                     return new UserCredential[size];
459                 }
460             };
461 
462         /**
463          * Get a unique identifier for UserCredential.
464          *
465          * @hide
466          * @return a Unique identifier for a UserCredential object
467          */
getUniqueId()468         public int getUniqueId() {
469             return Objects.hash(mUsername);
470         }
471     }
472     private UserCredential mUserCredential = null;
473     /**
474      * Set the user credential information.
475      *
476      * @param userCredential The user credential to set to
477      */
setUserCredential(UserCredential userCredential)478     public void setUserCredential(UserCredential userCredential) {
479         mUserCredential = userCredential;
480     }
481     /**
482      * Get the user credential information.
483      *
484      * @return user credential information
485      */
getUserCredential()486     public UserCredential getUserCredential() {
487         return mUserCredential;
488     }
489 
490     /**
491      * Certificate based credential.  This is used for EAP-TLS.
492      * Contains fields under PerProviderSubscription/Credential/DigitalCertificate subtree.
493      */
494     public static final class CertificateCredential implements Parcelable {
495         /**
496          * Supported certificate types.
497          * @hide
498          */
499         public static final String CERT_TYPE_X509V3 = "x509v3";
500 
501         /**
502          * Certificate SHA-256 fingerprint length.
503          */
504         private static final int CERT_SHA256_FINGER_PRINT_LENGTH = 32;
505 
506         /**
507          * Certificate type.
508          */
509         private String mCertType = null;
510         /**
511          * Set the certificate type associated with this certificate credential.
512          *
513          * @param certType The certificate type to set to
514          */
setCertType(String certType)515         public void setCertType(String certType) {
516             mCertType = certType;
517         }
518         /**
519          * Get the certificate type associated with this certificate credential.
520          *
521          * @return certificate type
522          */
getCertType()523         public String getCertType() {
524             return mCertType;
525         }
526 
527         /**
528          * The SHA-256 fingerprint of the certificate.
529          */
530         private byte[] mCertSha256Fingerprint = null;
531         /**
532          * Set the certificate SHA-256 fingerprint associated with this certificate credential.
533          *
534          * @param certSha256Fingerprint The certificate fingerprint to set to
535          */
setCertSha256Fingerprint(byte[] certSha256Fingerprint)536         public void setCertSha256Fingerprint(byte[] certSha256Fingerprint) {
537             mCertSha256Fingerprint = certSha256Fingerprint;
538         }
539         /**
540          * Get the certificate SHA-256 fingerprint associated with this certificate credential.
541          *
542          * @return certificate SHA-256 fingerprint
543          */
getCertSha256Fingerprint()544         public byte[] getCertSha256Fingerprint() {
545             return mCertSha256Fingerprint;
546         }
547 
548         /**
549          * Constructor for creating CertificateCredential with default values.
550          */
CertificateCredential()551         public CertificateCredential() {}
552 
553         /**
554          * Copy constructor.
555          *
556          * @param source The source to copy from
557          */
CertificateCredential(CertificateCredential source)558         public CertificateCredential(CertificateCredential source) {
559             if (source != null) {
560                 mCertType = source.mCertType;
561                 if (source.mCertSha256Fingerprint != null) {
562                     mCertSha256Fingerprint = Arrays.copyOf(source.mCertSha256Fingerprint,
563                                                           source.mCertSha256Fingerprint.length);
564                 }
565             }
566         }
567 
568         @Override
describeContents()569         public int describeContents() {
570             return 0;
571         }
572 
573         @Override
writeToParcel(Parcel dest, int flags)574         public void writeToParcel(Parcel dest, int flags) {
575             dest.writeString(mCertType);
576             dest.writeByteArray(mCertSha256Fingerprint);
577         }
578 
579         @Override
equals(Object thatObject)580         public boolean equals(Object thatObject) {
581             if (this == thatObject) {
582                 return true;
583             }
584             if (!(thatObject instanceof CertificateCredential)) {
585                 return false;
586             }
587 
588             CertificateCredential that = (CertificateCredential) thatObject;
589             return TextUtils.equals(mCertType, that.mCertType)
590                     && Arrays.equals(mCertSha256Fingerprint, that.mCertSha256Fingerprint);
591         }
592 
593         @Override
hashCode()594         public int hashCode() {
595             return Objects.hash(mCertType, Arrays.hashCode(mCertSha256Fingerprint));
596         }
597 
598         @Override
toString()599         public String toString() {
600             return "CertificateType: " + mCertType + "\n";
601         }
602 
603         /**
604          * Validate the configuration data.
605          *
606          * @return true on success or false on failure
607          * @hide
608          */
validate()609         public boolean validate() {
610             if (!TextUtils.equals(CERT_TYPE_X509V3, mCertType)) {
611                 Log.d(TAG, "Unsupported certificate type: " + mCertType);
612                 return false;
613             }
614             if (mCertSha256Fingerprint == null
615                     || mCertSha256Fingerprint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
616                 Log.d(TAG, "Invalid SHA-256 fingerprint");
617                 return false;
618             }
619             return true;
620         }
621 
622         public static final @android.annotation.NonNull Creator<CertificateCredential> CREATOR =
623             new Creator<CertificateCredential>() {
624                 @Override
625                 public CertificateCredential createFromParcel(Parcel in) {
626                     CertificateCredential certCredential = new CertificateCredential();
627                     certCredential.setCertType(in.readString());
628                     certCredential.setCertSha256Fingerprint(in.createByteArray());
629                     return certCredential;
630                 }
631 
632                 @Override
633                 public CertificateCredential[] newArray(int size) {
634                     return new CertificateCredential[size];
635                 }
636             };
637     }
638     private CertificateCredential mCertCredential = null;
639     /**
640      * Set the certificate credential information.
641      *
642      * @param certCredential The certificate credential to set to
643      */
setCertCredential(CertificateCredential certCredential)644     public void setCertCredential(CertificateCredential certCredential) {
645         mCertCredential = certCredential;
646     }
647     /**
648      * Get the certificate credential information.
649      *
650      * @return certificate credential information
651      */
getCertCredential()652     public CertificateCredential getCertCredential() {
653         return mCertCredential;
654     }
655 
656     /**
657      * SIM (Subscriber Identify Module) based credential.
658      * Contains fields under PerProviderSubscription/Credential/SIM subtree.
659      */
660     public static final class SimCredential implements Parcelable {
661         /**
662          * Maximum string length for IMSI.
663          */
664         private static final int MAX_IMSI_LENGTH = 15;
665 
666         /**
667          * International Mobile Subscriber Identity, is used to identify the user
668          * of a cellular network and is a unique identification associated with all
669          * cellular networks
670          */
671         private String mImsi = null;
672         /**
673          * Set the IMSI (International Mobile Subscriber Identity) associated with this SIM
674          * credential.
675          *
676          * @param imsi The IMSI to set to
677          */
setImsi(String imsi)678         public void setImsi(String imsi) {
679             mImsi = imsi;
680         }
681         /**
682          * Get the IMSI (International Mobile Subscriber Identity) associated with this SIM
683          * credential.
684          *
685          * @return IMSI associated with this SIM credential
686          */
getImsi()687         public String getImsi() {
688             return mImsi;
689         }
690 
691         /**
692          * EAP (Extensible Authentication Protocol) method type for using SIM credential.
693          * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
694          * for valid values.
695          * Using Integer.MIN_VALUE to indicate unset value.
696          */
697         private int mEapType = Integer.MIN_VALUE;
698         /**
699          * Set the EAP (Extensible Authentication Protocol) method type associated with this
700          * SIM credential.
701          *
702          * @param eapType The EAP method type to set to
703          */
setEapType(int eapType)704         public void setEapType(int eapType) {
705             mEapType = eapType;
706         }
707         /**
708          * Get the EAP (Extensible Authentication Protocol) method type associated with this
709          * SIM credential.
710          *
711          * @return EAP method type associated with this SIM credential
712          */
getEapType()713         public int getEapType() {
714             return mEapType;
715         }
716 
717         /**
718          * Constructor for creating SimCredential with default values.
719          */
SimCredential()720         public SimCredential() {}
721 
722         /**
723          * Copy constructor
724          *
725          * @param source The source to copy from
726          */
SimCredential(SimCredential source)727         public SimCredential(SimCredential source) {
728             if (source != null) {
729                 mImsi = source.mImsi;
730                 mEapType = source.mEapType;
731             }
732         }
733 
734         @Override
describeContents()735         public int describeContents() {
736             return 0;
737         }
738 
739         @Override
equals(Object thatObject)740         public boolean equals(Object thatObject) {
741             if (this == thatObject) {
742                 return true;
743             }
744             if (!(thatObject instanceof SimCredential)) {
745                 return false;
746             }
747 
748             SimCredential that = (SimCredential) thatObject;
749             return TextUtils.equals(mImsi, that.mImsi)
750                     && mEapType == that.mEapType;
751         }
752 
753         @Override
hashCode()754         public int hashCode() {
755             return Objects.hash(mImsi, mEapType);
756         }
757 
758         @Override
toString()759         public String toString() {
760             StringBuilder builder = new StringBuilder();
761             String imsi;
762             if (mImsi != null) {
763                 if (mImsi.length() > 6 && mImsi.charAt(6) != '*') {
764                     // Truncate the full IMSI from the log
765                     imsi = mImsi.substring(0, 6) + "****";
766                 } else {
767                     imsi = mImsi;
768                 }
769                 builder.append("IMSI: ").append(imsi).append("\n");
770             }
771             builder.append("EAPType: ").append(mEapType).append("\n");
772             return builder.toString();
773         }
774 
775         @Override
writeToParcel(Parcel dest, int flags)776         public void writeToParcel(Parcel dest, int flags) {
777             dest.writeString(mImsi);
778             dest.writeInt(mEapType);
779         }
780 
781         /**
782          * Validate the configuration data.
783          *
784          * @return true on success or false on failure
785          * @hide
786          */
validate()787         public boolean validate() {
788             // Note: this only validate the format of IMSI string itself.  Additional verification
789             // will be done by WifiService at the time of provisioning to verify against the IMSI
790             // of the SIM card installed in the device.
791             if (!verifyImsi()) {
792                 return false;
793             }
794             if (mEapType != EAPConstants.EAP_SIM && mEapType != EAPConstants.EAP_AKA
795                     && mEapType != EAPConstants.EAP_AKA_PRIME) {
796                 Log.d(TAG, "Invalid EAP Type for SIM credential: " + mEapType);
797                 return false;
798             }
799             return true;
800         }
801 
802         public static final @android.annotation.NonNull Creator<SimCredential> CREATOR =
803             new Creator<SimCredential>() {
804                 @Override
805                 public SimCredential createFromParcel(Parcel in) {
806                     SimCredential simCredential = new SimCredential();
807                     simCredential.setImsi(in.readString());
808                     simCredential.setEapType(in.readInt());
809                     return simCredential;
810                 }
811 
812                 @Override
813                 public SimCredential[] newArray(int size) {
814                     return new SimCredential[size];
815                 }
816             };
817 
818         /**
819          * Verify the IMSI (International Mobile Subscriber Identity) string.  The string
820          * should contain zero or more numeric digits, and might ends with a "*" for prefix
821          * matching.
822          *
823          * @return true if IMSI is valid, false otherwise.
824          */
verifyImsi()825         private boolean verifyImsi() {
826             if (TextUtils.isEmpty(mImsi)) {
827                 Log.d(TAG, "Missing IMSI");
828                 return false;
829             }
830             if (mImsi.length() > MAX_IMSI_LENGTH) {
831                 Log.d(TAG, "IMSI exceeding maximum length: " + mImsi.length());
832                 return false;
833             }
834 
835             // Locate the first non-digit character.
836             int nonDigit;
837             char stopChar = '\0';
838             for (nonDigit = 0; nonDigit < mImsi.length(); nonDigit++) {
839                 stopChar = mImsi.charAt(nonDigit);
840                 if (stopChar < '0' || stopChar > '9') {
841                     break;
842                 }
843             }
844 
845             if (nonDigit == mImsi.length()) {
846                 return true;
847             }
848             else if (nonDigit == mImsi.length()-1 && stopChar == '*') {
849                 // Prefix matching.
850                 return true;
851             }
852             return false;
853         }
854     }
855     private SimCredential mSimCredential = null;
856     /**
857      * Set the SIM credential information.
858      *
859      * @param simCredential The SIM credential to set to
860      */
setSimCredential(SimCredential simCredential)861     public void setSimCredential(SimCredential simCredential) {
862         mSimCredential = simCredential;
863     }
864     /**
865      * Get the SIM credential information.
866      *
867      * @return SIM credential information
868      */
getSimCredential()869     public SimCredential getSimCredential() {
870         return mSimCredential;
871     }
872 
873     /**
874      * CA (Certificate Authority) X509 certificates.
875      */
876     private X509Certificate[] mCaCertificates = null;
877 
878     /**
879      * Set the CA (Certification Authority) certificate associated with this credential.
880      *
881      * @param caCertificate The CA certificate to set to
882      */
setCaCertificate(X509Certificate caCertificate)883     public void setCaCertificate(X509Certificate caCertificate) {
884         mCaCertificates = null;
885         if (caCertificate != null) {
886             mCaCertificates = new X509Certificate[] {caCertificate};
887         }
888     }
889 
890     /**
891      * Set the CA (Certification Authority) certificates associated with this credential.
892      *
893      * @param caCertificates The list of CA certificates to set to
894      * @hide
895      */
setCaCertificates(X509Certificate[] caCertificates)896     public void setCaCertificates(X509Certificate[] caCertificates) {
897         mCaCertificates = caCertificates;
898     }
899 
900     /**
901      * Get the CA (Certification Authority) certificate associated with this credential.
902      *
903      * @return CA certificate associated with this credential, {@code null} if certificate is not
904      * set or certificate is more than one.
905      */
getCaCertificate()906     public X509Certificate getCaCertificate() {
907         return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0];
908     }
909 
910     /**
911      * Get the CA (Certification Authority) certificates associated with this credential.
912      *
913      * @return The list of CA certificates associated with this credential
914      * @hide
915      */
getCaCertificates()916     public X509Certificate[] getCaCertificates() {
917         return mCaCertificates;
918     }
919 
920     /**
921      * Client side X509 certificate chain.
922      */
923     private X509Certificate[] mClientCertificateChain = null;
924     /**
925      * Set the client certificate chain associated with this credential.
926      *
927      * @param certificateChain The client certificate chain to set to
928      */
setClientCertificateChain(X509Certificate[] certificateChain)929     public void setClientCertificateChain(X509Certificate[] certificateChain) {
930         mClientCertificateChain = certificateChain;
931     }
932     /**
933      * Get the client certificate chain associated with this credential.
934      *
935      * @return client certificate chain associated with this credential
936      */
getClientCertificateChain()937     public X509Certificate[] getClientCertificateChain() {
938         return mClientCertificateChain;
939     }
940 
941     /**
942      * Client side private key.
943      */
944     private PrivateKey mClientPrivateKey = null;
945     /**
946      * Set the client private key associated with this credential.
947      *
948      * @param clientPrivateKey the client private key to set to
949      */
setClientPrivateKey(PrivateKey clientPrivateKey)950     public void setClientPrivateKey(PrivateKey clientPrivateKey) {
951         mClientPrivateKey = clientPrivateKey;
952     }
953     /**
954      * Get the client private key associated with this credential.
955      *
956      * @return client private key associated with this credential.
957      */
getClientPrivateKey()958     public PrivateKey getClientPrivateKey() {
959         return mClientPrivateKey;
960     }
961 
962     /**
963      * The required minimum TLS version.
964      */
965     private @WifiEnterpriseConfig.TlsVersion int mMinimumTlsVersion = WifiEnterpriseConfig.TLS_V1_0;
966     /**
967      * Set the minimum TLS version for TLS-based EAP methods.
968      *
969      * {@link android.net.wifi.WifiManager#isTlsMinimumVersionSupported()} indicates whether
970      * or not a minimum TLS version can be set. If not supported, the minimum TLS version
971      * is always TLS v1.0.
972      * <p>
973      * {@link android.net.wifi.WifiManager#isTlsV13Supported()} indicates whether or not
974      * TLS v1.3 is supported. If requested minimum is not supported, it will default to
975      * the maximum supported version.
976      *
977      * @param tlsVersion the TLS version
978      * @throws IllegalArgumentException if the TLS version is invalid.
979      */
setMinimumTlsVersion(@ifiEnterpriseConfig.TlsVersion int tlsVersion)980     public void setMinimumTlsVersion(@WifiEnterpriseConfig.TlsVersion int tlsVersion)
981             throws IllegalArgumentException {
982         if (tlsVersion < WifiEnterpriseConfig.TLS_VERSION_MIN
983                 || tlsVersion > WifiEnterpriseConfig.TLS_VERSION_MAX) {
984             throw new IllegalArgumentException(
985                     "Invalid TLS version: " + tlsVersion);
986         }
987         mMinimumTlsVersion = tlsVersion;
988     }
989 
990     /**
991      * Get the minimum TLS version for TLS-based EAP methods.
992      *
993      * @return the TLS version
994      */
getMinimumTlsVersion()995     public @WifiEnterpriseConfig.TlsVersion int getMinimumTlsVersion() {
996         return mMinimumTlsVersion;
997     }
998 
999     /**
1000      * Constructor for creating Credential with default values.
1001      */
Credential()1002     public Credential() {}
1003 
1004     /**
1005      * Copy constructor.
1006      *
1007      * @param source The source to copy from
1008      */
Credential(Credential source)1009     public Credential(Credential source) {
1010         if (source != null) {
1011             mCreationTimeInMillis = source.mCreationTimeInMillis;
1012             mExpirationTimeInMillis = source.mExpirationTimeInMillis;
1013             mRealm = source.mRealm;
1014             mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus;
1015             if (source.mUserCredential != null) {
1016                 mUserCredential = new UserCredential(source.mUserCredential);
1017             }
1018             if (source.mCertCredential != null) {
1019                 mCertCredential = new CertificateCredential(source.mCertCredential);
1020             }
1021             if (source.mSimCredential != null) {
1022                 mSimCredential = new SimCredential(source.mSimCredential);
1023             }
1024             if (source.mClientCertificateChain != null) {
1025                 mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
1026                                                         source.mClientCertificateChain.length);
1027             }
1028             if (source.mCaCertificates != null) {
1029                 mCaCertificates = Arrays.copyOf(source.mCaCertificates,
1030                         source.mCaCertificates.length);
1031             }
1032 
1033             mClientPrivateKey = source.mClientPrivateKey;
1034             mMinimumTlsVersion = source.mMinimumTlsVersion;
1035         }
1036     }
1037 
1038     @Override
describeContents()1039     public int describeContents() {
1040         return 0;
1041     }
1042 
1043     @Override
writeToParcel(Parcel dest, int flags)1044     public void writeToParcel(Parcel dest, int flags) {
1045         dest.writeLong(mCreationTimeInMillis);
1046         dest.writeLong(mExpirationTimeInMillis);
1047         dest.writeString(mRealm);
1048         dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0);
1049         dest.writeParcelable(mUserCredential, flags);
1050         dest.writeParcelable(mCertCredential, flags);
1051         dest.writeParcelable(mSimCredential, flags);
1052         ParcelUtil.writeCertificates(dest, mCaCertificates);
1053         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
1054         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
1055         dest.writeInt(mMinimumTlsVersion);
1056     }
1057 
1058     @Override
equals(Object thatObject)1059     public boolean equals(Object thatObject) {
1060         if (this == thatObject) {
1061             return true;
1062         }
1063         if (!(thatObject instanceof Credential)) {
1064             return false;
1065         }
1066 
1067         Credential that = (Credential) thatObject;
1068         return TextUtils.equals(mRealm, that.mRealm)
1069                 && mCreationTimeInMillis == that.mCreationTimeInMillis
1070                 && mExpirationTimeInMillis == that.mExpirationTimeInMillis
1071                 && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus
1072                 && (mUserCredential == null ? that.mUserCredential == null
1073                     : mUserCredential.equals(that.mUserCredential))
1074                 && (mCertCredential == null ? that.mCertCredential == null
1075                     : mCertCredential.equals(that.mCertCredential))
1076                 && (mSimCredential == null ? that.mSimCredential == null
1077                     : mSimCredential.equals(that.mSimCredential))
1078                 && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates)
1079                 && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
1080                 && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey)
1081                 && mMinimumTlsVersion == that.mMinimumTlsVersion;
1082     }
1083 
1084     @Override
hashCode()1085     public int hashCode() {
1086         return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm,
1087                 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
1088                 mClientPrivateKey, Arrays.hashCode(mCaCertificates),
1089                 Arrays.hashCode(mClientCertificateChain), mMinimumTlsVersion);
1090     }
1091 
1092     /**
1093      * Get a unique identifier for Credential. This identifier depends only on items that remain
1094      * constant throughout the lifetime of a subscription's credentials.
1095      *
1096      * @hide
1097      * @return a Unique identifier for a Credential object
1098      */
getUniqueId()1099     public int getUniqueId() {
1100         return Objects.hash(mUserCredential != null ? mUserCredential.getUniqueId() : 0,
1101                 mCertCredential, mSimCredential, mRealm);
1102     }
1103 
1104     @Override
toString()1105     public String toString() {
1106         StringBuilder builder = new StringBuilder();
1107         builder.append("Realm: ").append(mRealm).append("\n");
1108         builder.append("CreationTime: ").append(mCreationTimeInMillis != Long.MIN_VALUE
1109                 ? new Date(mCreationTimeInMillis) : "Not specified").append("\n");
1110         builder.append("ExpirationTime: ").append(mExpirationTimeInMillis != Long.MIN_VALUE
1111                 ? new Date(mExpirationTimeInMillis) : "Not specified").append("\n");
1112         builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n");
1113         if (mUserCredential != null) {
1114             builder.append("UserCredential Begin ---\n");
1115             builder.append(mUserCredential);
1116             builder.append("UserCredential End ---\n");
1117         }
1118         if (mCertCredential != null) {
1119             builder.append("CertificateCredential Begin ---\n");
1120             builder.append(mCertCredential);
1121             builder.append("CertificateCredential End ---\n");
1122         }
1123         builder.append("MinimumTlsVersion: ").append(mMinimumTlsVersion).append("\n");
1124         if (mSimCredential != null) {
1125             builder.append("SIMCredential Begin ---\n");
1126             builder.append(mSimCredential);
1127             builder.append("SIMCredential End ---\n");
1128         }
1129         return builder.toString();
1130     }
1131 
1132     /**
1133      * Validate the configuration data.
1134      *
1135      * @return true on success or false on failure
1136      * @hide
1137      */
validate()1138     public boolean validate() {
1139         if (TextUtils.isEmpty(mRealm)) {
1140             Log.d(TAG, "Missing realm");
1141             return false;
1142         }
1143         if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
1144             Log.d(TAG, "realm exceeding maximum length: "
1145                     + mRealm.getBytes(StandardCharsets.UTF_8).length);
1146             return false;
1147         }
1148 
1149         // Verify the credential.
1150         if (mUserCredential != null) {
1151             if (!verifyUserCredential()) {
1152                 return false;
1153             }
1154         } else if (mCertCredential != null) {
1155             if (!verifyCertCredential()) {
1156                 return false;
1157             }
1158         } else if (mSimCredential != null) {
1159             if (!verifySimCredential()) {
1160                 return false;
1161             }
1162         } else {
1163             Log.d(TAG, "Missing required credential");
1164             return false;
1165         }
1166 
1167         return true;
1168     }
1169 
1170     public static final @android.annotation.NonNull Creator<Credential> CREATOR =
1171         new Creator<Credential>() {
1172             @Override
1173             public Credential createFromParcel(Parcel in) {
1174                 Credential credential = new Credential();
1175                 credential.setCreationTimeInMillis(in.readLong());
1176                 credential.setExpirationTimeInMillis(in.readLong());
1177                 credential.setRealm(in.readString());
1178                 credential.setCheckAaaServerCertStatus(in.readInt() != 0);
1179                 credential.setUserCredential(in.readParcelable(null));
1180                 credential.setCertCredential(in.readParcelable(null));
1181                 credential.setSimCredential(in.readParcelable(null));
1182                 credential.setCaCertificates(ParcelUtil.readCertificates(in));
1183                 credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
1184                 credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
1185                 credential.setMinimumTlsVersion(in.readInt());
1186                 return credential;
1187             }
1188 
1189             @Override
1190             public Credential[] newArray(int size) {
1191                 return new Credential[size];
1192             }
1193         };
1194 
1195     /**
1196      * Verify user credential.
1197      * If no CA certificate is provided, then the system uses the CAs in the trust store.
1198      *
1199      * @return true if user credential is valid, false otherwise.
1200      */
verifyUserCredential()1201     private boolean verifyUserCredential() {
1202         if (mUserCredential == null) {
1203             Log.d(TAG, "Missing user credential");
1204             return false;
1205         }
1206         if (mCertCredential != null || mSimCredential != null) {
1207             Log.d(TAG, "Contained more than one type of credential");
1208             return false;
1209         }
1210         if (!mUserCredential.validate()) {
1211             return false;
1212         }
1213 
1214         return true;
1215     }
1216 
1217     /**
1218      * Verify certificate credential, which is used for EAP-TLS.  This will verify
1219      * that the necessary client key and certificates are provided.
1220      * If no CA certificate is provided, then the system uses the CAs in the trust store.
1221      *
1222      * @return true if certificate credential is valid, false otherwise.
1223      */
verifyCertCredential()1224     private boolean verifyCertCredential() {
1225         if (mCertCredential == null) {
1226             Log.d(TAG, "Missing certificate credential");
1227             return false;
1228         }
1229         if (mUserCredential != null || mSimCredential != null) {
1230             Log.d(TAG, "Contained more than one type of credential");
1231             return false;
1232         }
1233 
1234         if (!mCertCredential.validate()) {
1235             return false;
1236         }
1237 
1238         if (mClientPrivateKey == null) {
1239             Log.d(TAG, "Missing client private key for certificate credential");
1240             return false;
1241         }
1242         try {
1243             // Verify SHA-256 fingerprint for client certificate.
1244             if (!verifySha256Fingerprint(mClientCertificateChain,
1245                     mCertCredential.getCertSha256Fingerprint())) {
1246                 Log.d(TAG, "SHA-256 fingerprint mismatch");
1247                 return false;
1248             }
1249         } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
1250             Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage());
1251             return false;
1252         }
1253 
1254         return true;
1255     }
1256 
1257     /**
1258      * Verify SIM credential.
1259      *
1260      * @return true if SIM credential is valid, false otherwise.
1261      */
verifySimCredential()1262     private boolean verifySimCredential() {
1263         if (mSimCredential == null) {
1264             Log.d(TAG, "Missing SIM credential");
1265             return false;
1266         }
1267         if (mUserCredential != null || mCertCredential != null) {
1268             Log.d(TAG, "Contained more than one type of credential");
1269             return false;
1270         }
1271         return mSimCredential.validate();
1272     }
1273 
isPrivateKeyEquals(PrivateKey key1, PrivateKey key2)1274     private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) {
1275         if (key1 == null && key2 == null) {
1276             return true;
1277         }
1278 
1279         /* Return false if only one of them is null */
1280         if (key1 == null || key2 == null) {
1281             return false;
1282         }
1283 
1284         return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) &&
1285                 Arrays.equals(key1.getEncoded(), key2.getEncoded());
1286     }
1287 
1288     /**
1289      * Verify two X.509 certificates are identical.
1290      *
1291      * @param cert1 a certificate to compare
1292      * @param cert2 a certificate to compare
1293      * @return {@code true} if given certificates are the same each other, {@code false} otherwise.
1294      * @hide
1295      */
isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2)1296     public static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) {
1297         if (cert1 == null && cert2 == null) {
1298             return true;
1299         }
1300 
1301         /* Return false if only one of them is null */
1302         if (cert1 == null || cert2 == null) {
1303             return false;
1304         }
1305 
1306         boolean result = false;
1307         try {
1308             result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded());
1309         } catch (CertificateEncodingException e) {
1310             /* empty, return false. */
1311         }
1312         return result;
1313     }
1314 
isX509CertificatesEquals(X509Certificate[] certs1, X509Certificate[] certs2)1315     private static boolean isX509CertificatesEquals(X509Certificate[] certs1,
1316                                                     X509Certificate[] certs2) {
1317         if (certs1 == null && certs2 == null) {
1318             return true;
1319         }
1320 
1321         /* Return false if only one of them is null */
1322         if (certs1 == null || certs2 == null) {
1323             return false;
1324         }
1325 
1326         if (certs1.length != certs2.length) {
1327             return false;
1328         }
1329 
1330         for (int i = 0; i < certs1.length; i++) {
1331             if (!isX509CertificateEquals(certs1[i], certs2[i])) {
1332                 return false;
1333             }
1334         }
1335 
1336         return true;
1337     }
1338 
1339     /**
1340      * Verify that the digest for a certificate in the certificate chain matches expected
1341      * fingerprint.  The certificate that matches the fingerprint is the client certificate.
1342      *
1343      * @param certChain Chain of certificates
1344      * @param expectedFingerprint The expected SHA-256 digest of the client certificate
1345      * @return true if the certificate chain contains a matching certificate, false otherwise
1346      * @throws NoSuchAlgorithmException
1347      * @throws CertificateEncodingException
1348      */
verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint)1349     private static boolean verifySha256Fingerprint(X509Certificate[] certChain,
1350                                                    byte[] expectedFingerprint)
1351             throws NoSuchAlgorithmException, CertificateEncodingException {
1352         if (certChain == null) {
1353             return false;
1354         }
1355         MessageDigest digester = MessageDigest.getInstance("SHA-256");
1356         for (X509Certificate certificate : certChain) {
1357             digester.reset();
1358             byte[] fingerprint = digester.digest(certificate.getEncoded());
1359             if (Arrays.equals(expectedFingerprint, fingerprint)) {
1360                 return true;
1361             }
1362         }
1363         return false;
1364     }
1365 }
1366