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