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     private UserCredential mUserCredential = null;
453     /**
454      * Set the user credential information.
455      *
456      * @param userCredential The user credential to set to
457      */
setUserCredential(UserCredential userCredential)458     public void setUserCredential(UserCredential userCredential) {
459         mUserCredential = userCredential;
460     }
461     /**
462      * Get the user credential information.
463      *
464      * @return user credential information
465      */
getUserCredential()466     public UserCredential getUserCredential() {
467         return mUserCredential;
468     }
469 
470     /**
471      * Certificate based credential.  This is used for EAP-TLS.
472      * Contains fields under PerProviderSubscription/Credential/DigitalCertificate subtree.
473      */
474     public static final class CertificateCredential implements Parcelable {
475         /**
476          * Supported certificate types.
477          * @hide
478          */
479         public static final String CERT_TYPE_X509V3 = "x509v3";
480 
481         /**
482          * Certificate SHA-256 fingerprint length.
483          */
484         private static final int CERT_SHA256_FINGER_PRINT_LENGTH = 32;
485 
486         /**
487          * Certificate type.
488          */
489         private String mCertType = null;
490         /**
491          * Set the certificate type associated with this certificate credential.
492          *
493          * @param certType The certificate type to set to
494          */
setCertType(String certType)495         public void setCertType(String certType) {
496             mCertType = certType;
497         }
498         /**
499          * Get the certificate type associated with this certificate credential.
500          *
501          * @return certificate type
502          */
getCertType()503         public String getCertType() {
504             return mCertType;
505         }
506 
507         /**
508          * The SHA-256 fingerprint of the certificate.
509          */
510         private byte[] mCertSha256Fingerprint = null;
511         /**
512          * Set the certificate SHA-256 fingerprint associated with this certificate credential.
513          *
514          * @param certSha256Fingerprint The certificate fingerprint to set to
515          */
setCertSha256Fingerprint(byte[] certSha256Fingerprint)516         public void setCertSha256Fingerprint(byte[] certSha256Fingerprint) {
517             mCertSha256Fingerprint = certSha256Fingerprint;
518         }
519         /**
520          * Get the certificate SHA-256 fingerprint associated with this certificate credential.
521          *
522          * @return certificate SHA-256 fingerprint
523          */
getCertSha256Fingerprint()524         public byte[] getCertSha256Fingerprint() {
525             return mCertSha256Fingerprint;
526         }
527 
528         /**
529          * Constructor for creating CertificateCredential with default values.
530          */
CertificateCredential()531         public CertificateCredential() {}
532 
533         /**
534          * Copy constructor.
535          *
536          * @param source The source to copy from
537          */
CertificateCredential(CertificateCredential source)538         public CertificateCredential(CertificateCredential source) {
539             if (source != null) {
540                 mCertType = source.mCertType;
541                 if (source.mCertSha256Fingerprint != null) {
542                     mCertSha256Fingerprint = Arrays.copyOf(source.mCertSha256Fingerprint,
543                                                           source.mCertSha256Fingerprint.length);
544                 }
545             }
546         }
547 
548         @Override
describeContents()549         public int describeContents() {
550             return 0;
551         }
552 
553         @Override
writeToParcel(Parcel dest, int flags)554         public void writeToParcel(Parcel dest, int flags) {
555             dest.writeString(mCertType);
556             dest.writeByteArray(mCertSha256Fingerprint);
557         }
558 
559         @Override
equals(Object thatObject)560         public boolean equals(Object thatObject) {
561             if (this == thatObject) {
562                 return true;
563             }
564             if (!(thatObject instanceof CertificateCredential)) {
565                 return false;
566             }
567 
568             CertificateCredential that = (CertificateCredential) thatObject;
569             return TextUtils.equals(mCertType, that.mCertType)
570                     && Arrays.equals(mCertSha256Fingerprint, that.mCertSha256Fingerprint);
571         }
572 
573         @Override
hashCode()574         public int hashCode() {
575             return Objects.hash(mCertType, Arrays.hashCode(mCertSha256Fingerprint));
576         }
577 
578         @Override
toString()579         public String toString() {
580             return "CertificateType: " + mCertType + "\n";
581         }
582 
583         /**
584          * Validate the configuration data.
585          *
586          * @return true on success or false on failure
587          * @hide
588          */
validate()589         public boolean validate() {
590             if (!TextUtils.equals(CERT_TYPE_X509V3, mCertType)) {
591                 Log.d(TAG, "Unsupported certificate type: " + mCertType);
592                 return false;
593             }
594             if (mCertSha256Fingerprint == null
595                     || mCertSha256Fingerprint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
596                 Log.d(TAG, "Invalid SHA-256 fingerprint");
597                 return false;
598             }
599             return true;
600         }
601 
602         public static final @android.annotation.NonNull Creator<CertificateCredential> CREATOR =
603             new Creator<CertificateCredential>() {
604                 @Override
605                 public CertificateCredential createFromParcel(Parcel in) {
606                     CertificateCredential certCredential = new CertificateCredential();
607                     certCredential.setCertType(in.readString());
608                     certCredential.setCertSha256Fingerprint(in.createByteArray());
609                     return certCredential;
610                 }
611 
612                 @Override
613                 public CertificateCredential[] newArray(int size) {
614                     return new CertificateCredential[size];
615                 }
616             };
617     }
618     private CertificateCredential mCertCredential = null;
619     /**
620      * Set the certificate credential information.
621      *
622      * @param certCredential The certificate credential to set to
623      */
setCertCredential(CertificateCredential certCredential)624     public void setCertCredential(CertificateCredential certCredential) {
625         mCertCredential = certCredential;
626     }
627     /**
628      * Get the certificate credential information.
629      *
630      * @return certificate credential information
631      */
getCertCredential()632     public CertificateCredential getCertCredential() {
633         return mCertCredential;
634     }
635 
636     /**
637      * SIM (Subscriber Identify Module) based credential.
638      * Contains fields under PerProviderSubscription/Credential/SIM subtree.
639      */
640     public static final class SimCredential implements Parcelable {
641         /**
642          * Maximum string length for IMSI.
643          */
644         private static final int MAX_IMSI_LENGTH = 15;
645 
646         /**
647          * International Mobile Subscriber Identity, is used to identify the user
648          * of a cellular network and is a unique identification associated with all
649          * cellular networks
650          */
651         private String mImsi = null;
652         /**
653          * Set the IMSI (International Mobile Subscriber Identity) associated with this SIM
654          * credential.
655          *
656          * @param imsi The IMSI to set to
657          */
setImsi(String imsi)658         public void setImsi(String imsi) {
659             mImsi = imsi;
660         }
661         /**
662          * Get the IMSI (International Mobile Subscriber Identity) associated with this SIM
663          * credential.
664          *
665          * @return IMSI associated with this SIM credential
666          */
getImsi()667         public String getImsi() {
668             return mImsi;
669         }
670 
671         /**
672          * EAP (Extensible Authentication Protocol) method type for using SIM credential.
673          * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
674          * for valid values.
675          * Using Integer.MIN_VALUE to indicate unset value.
676          */
677         private int mEapType = Integer.MIN_VALUE;
678         /**
679          * Set the EAP (Extensible Authentication Protocol) method type associated with this
680          * SIM credential.
681          *
682          * @param eapType The EAP method type to set to
683          */
setEapType(int eapType)684         public void setEapType(int eapType) {
685             mEapType = eapType;
686         }
687         /**
688          * Get the EAP (Extensible Authentication Protocol) method type associated with this
689          * SIM credential.
690          *
691          * @return EAP method type associated with this SIM credential
692          */
getEapType()693         public int getEapType() {
694             return mEapType;
695         }
696 
697         /**
698          * Constructor for creating SimCredential with default values.
699          */
SimCredential()700         public SimCredential() {}
701 
702         /**
703          * Copy constructor
704          *
705          * @param source The source to copy from
706          */
SimCredential(SimCredential source)707         public SimCredential(SimCredential source) {
708             if (source != null) {
709                 mImsi = source.mImsi;
710                 mEapType = source.mEapType;
711             }
712         }
713 
714         @Override
describeContents()715         public int describeContents() {
716             return 0;
717         }
718 
719         @Override
equals(Object thatObject)720         public boolean equals(Object thatObject) {
721             if (this == thatObject) {
722                 return true;
723             }
724             if (!(thatObject instanceof SimCredential)) {
725                 return false;
726             }
727 
728             SimCredential that = (SimCredential) thatObject;
729             return TextUtils.equals(mImsi, that.mImsi)
730                     && mEapType == that.mEapType;
731         }
732 
733         @Override
hashCode()734         public int hashCode() {
735             return Objects.hash(mImsi, mEapType);
736         }
737 
738         @Override
toString()739         public String toString() {
740             StringBuilder builder = new StringBuilder();
741             String imsi;
742             if (mImsi != null) {
743                 if (mImsi.length() > 6 && mImsi.charAt(6) != '*') {
744                     // Truncate the full IMSI from the log
745                     imsi = mImsi.substring(0, 6) + "****";
746                 } else {
747                     imsi = mImsi;
748                 }
749                 builder.append("IMSI: ").append(imsi).append("\n");
750             }
751             builder.append("EAPType: ").append(mEapType).append("\n");
752             return builder.toString();
753         }
754 
755         @Override
writeToParcel(Parcel dest, int flags)756         public void writeToParcel(Parcel dest, int flags) {
757             dest.writeString(mImsi);
758             dest.writeInt(mEapType);
759         }
760 
761         /**
762          * Validate the configuration data.
763          *
764          * @return true on success or false on failure
765          * @hide
766          */
validate()767         public boolean validate() {
768             // Note: this only validate the format of IMSI string itself.  Additional verification
769             // will be done by WifiService at the time of provisioning to verify against the IMSI
770             // of the SIM card installed in the device.
771             if (!verifyImsi()) {
772                 return false;
773             }
774             if (mEapType != EAPConstants.EAP_SIM && mEapType != EAPConstants.EAP_AKA
775                     && mEapType != EAPConstants.EAP_AKA_PRIME) {
776                 Log.d(TAG, "Invalid EAP Type for SIM credential: " + mEapType);
777                 return false;
778             }
779             return true;
780         }
781 
782         public static final @android.annotation.NonNull Creator<SimCredential> CREATOR =
783             new Creator<SimCredential>() {
784                 @Override
785                 public SimCredential createFromParcel(Parcel in) {
786                     SimCredential simCredential = new SimCredential();
787                     simCredential.setImsi(in.readString());
788                     simCredential.setEapType(in.readInt());
789                     return simCredential;
790                 }
791 
792                 @Override
793                 public SimCredential[] newArray(int size) {
794                     return new SimCredential[size];
795                 }
796             };
797 
798         /**
799          * Verify the IMSI (International Mobile Subscriber Identity) string.  The string
800          * should contain zero or more numeric digits, and might ends with a "*" for prefix
801          * matching.
802          *
803          * @return true if IMSI is valid, false otherwise.
804          */
verifyImsi()805         private boolean verifyImsi() {
806             if (TextUtils.isEmpty(mImsi)) {
807                 Log.d(TAG, "Missing IMSI");
808                 return false;
809             }
810             if (mImsi.length() > MAX_IMSI_LENGTH) {
811                 Log.d(TAG, "IMSI exceeding maximum length: " + mImsi.length());
812                 return false;
813             }
814 
815             // Locate the first non-digit character.
816             int nonDigit;
817             char stopChar = '\0';
818             for (nonDigit = 0; nonDigit < mImsi.length(); nonDigit++) {
819                 stopChar = mImsi.charAt(nonDigit);
820                 if (stopChar < '0' || stopChar > '9') {
821                     break;
822                 }
823             }
824 
825             if (nonDigit == mImsi.length()) {
826                 return true;
827             }
828             else if (nonDigit == mImsi.length()-1 && stopChar == '*') {
829                 // Prefix matching.
830                 return true;
831             }
832             return false;
833         }
834     }
835     private SimCredential mSimCredential = null;
836     /**
837      * Set the SIM credential information.
838      *
839      * @param simCredential The SIM credential to set to
840      */
setSimCredential(SimCredential simCredential)841     public void setSimCredential(SimCredential simCredential) {
842         mSimCredential = simCredential;
843     }
844     /**
845      * Get the SIM credential information.
846      *
847      * @return SIM credential information
848      */
getSimCredential()849     public SimCredential getSimCredential() {
850         return mSimCredential;
851     }
852 
853     /**
854      * CA (Certificate Authority) X509 certificates.
855      */
856     private X509Certificate[] mCaCertificates = null;
857 
858     /**
859      * Set the CA (Certification Authority) certificate associated with this credential.
860      *
861      * @param caCertificate The CA certificate to set to
862      */
setCaCertificate(X509Certificate caCertificate)863     public void setCaCertificate(X509Certificate caCertificate) {
864         mCaCertificates = null;
865         if (caCertificate != null) {
866             mCaCertificates = new X509Certificate[] {caCertificate};
867         }
868     }
869 
870     /**
871      * Set the CA (Certification Authority) certificates associated with this credential.
872      *
873      * @param caCertificates The list of CA certificates to set to
874      * @hide
875      */
setCaCertificates(X509Certificate[] caCertificates)876     public void setCaCertificates(X509Certificate[] caCertificates) {
877         mCaCertificates = caCertificates;
878     }
879 
880     /**
881      * Get the CA (Certification Authority) certificate associated with this credential.
882      *
883      * @return CA certificate associated with this credential, {@code null} if certificate is not
884      * set or certificate is more than one.
885      */
getCaCertificate()886     public X509Certificate getCaCertificate() {
887         return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0];
888     }
889 
890     /**
891      * Get the CA (Certification Authority) certificates associated with this credential.
892      *
893      * @return The list of CA certificates associated with this credential
894      * @hide
895      */
getCaCertificates()896     public X509Certificate[] getCaCertificates() {
897         return mCaCertificates;
898     }
899 
900     /**
901      * Client side X509 certificate chain.
902      */
903     private X509Certificate[] mClientCertificateChain = null;
904     /**
905      * Set the client certificate chain associated with this credential.
906      *
907      * @param certificateChain The client certificate chain to set to
908      */
setClientCertificateChain(X509Certificate[] certificateChain)909     public void setClientCertificateChain(X509Certificate[] certificateChain) {
910         mClientCertificateChain = certificateChain;
911     }
912     /**
913      * Get the client certificate chain associated with this credential.
914      *
915      * @return client certificate chain associated with this credential
916      */
getClientCertificateChain()917     public X509Certificate[] getClientCertificateChain() {
918         return mClientCertificateChain;
919     }
920 
921     /**
922      * Client side private key.
923      */
924     private PrivateKey mClientPrivateKey = null;
925     /**
926      * Set the client private key associated with this credential.
927      *
928      * @param clientPrivateKey the client private key to set to
929      */
setClientPrivateKey(PrivateKey clientPrivateKey)930     public void setClientPrivateKey(PrivateKey clientPrivateKey) {
931         mClientPrivateKey = clientPrivateKey;
932     }
933     /**
934      * Get the client private key associated with this credential.
935      *
936      * @return client private key associated with this credential.
937      */
getClientPrivateKey()938     public PrivateKey getClientPrivateKey() {
939         return mClientPrivateKey;
940     }
941 
942     /**
943      * Constructor for creating Credential with default values.
944      */
Credential()945     public Credential() {}
946 
947     /**
948      * Copy constructor.
949      *
950      * @param source The source to copy from
951      */
Credential(Credential source)952     public Credential(Credential source) {
953         if (source != null) {
954             mCreationTimeInMillis = source.mCreationTimeInMillis;
955             mExpirationTimeInMillis = source.mExpirationTimeInMillis;
956             mRealm = source.mRealm;
957             mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus;
958             if (source.mUserCredential != null) {
959                 mUserCredential = new UserCredential(source.mUserCredential);
960             }
961             if (source.mCertCredential != null) {
962                 mCertCredential = new CertificateCredential(source.mCertCredential);
963             }
964             if (source.mSimCredential != null) {
965                 mSimCredential = new SimCredential(source.mSimCredential);
966             }
967             if (source.mClientCertificateChain != null) {
968                 mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
969                                                         source.mClientCertificateChain.length);
970             }
971             if (source.mCaCertificates != null) {
972                 mCaCertificates = Arrays.copyOf(source.mCaCertificates,
973                         source.mCaCertificates.length);
974             }
975 
976             mClientPrivateKey = source.mClientPrivateKey;
977         }
978     }
979 
980     @Override
describeContents()981     public int describeContents() {
982         return 0;
983     }
984 
985     @Override
writeToParcel(Parcel dest, int flags)986     public void writeToParcel(Parcel dest, int flags) {
987         dest.writeLong(mCreationTimeInMillis);
988         dest.writeLong(mExpirationTimeInMillis);
989         dest.writeString(mRealm);
990         dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0);
991         dest.writeParcelable(mUserCredential, flags);
992         dest.writeParcelable(mCertCredential, flags);
993         dest.writeParcelable(mSimCredential, flags);
994         ParcelUtil.writeCertificates(dest, mCaCertificates);
995         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
996         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
997     }
998 
999     @Override
equals(Object thatObject)1000     public boolean equals(Object thatObject) {
1001         if (this == thatObject) {
1002             return true;
1003         }
1004         if (!(thatObject instanceof Credential)) {
1005             return false;
1006         }
1007 
1008         Credential that = (Credential) thatObject;
1009         return TextUtils.equals(mRealm, that.mRealm)
1010                 && mCreationTimeInMillis == that.mCreationTimeInMillis
1011                 && mExpirationTimeInMillis == that.mExpirationTimeInMillis
1012                 && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus
1013                 && (mUserCredential == null ? that.mUserCredential == null
1014                     : mUserCredential.equals(that.mUserCredential))
1015                 && (mCertCredential == null ? that.mCertCredential == null
1016                     : mCertCredential.equals(that.mCertCredential))
1017                 && (mSimCredential == null ? that.mSimCredential == null
1018                     : mSimCredential.equals(that.mSimCredential))
1019                 && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates)
1020                 && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
1021                 && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey);
1022     }
1023 
1024     @Override
hashCode()1025     public int hashCode() {
1026         return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm,
1027                 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
1028                 mClientPrivateKey, Arrays.hashCode(mCaCertificates),
1029                 Arrays.hashCode(mClientCertificateChain));
1030     }
1031 
1032     /**
1033      * Get a unique identifier for Credential. This identifier depends only on items that remain
1034      * constant throughout the lifetime of a subscription's credentials.
1035      *
1036      * @hide
1037      * @return a Unique identifier for a Credential object
1038      */
getUniqueId()1039     public int getUniqueId() {
1040         return Objects.hash(mUserCredential, mCertCredential, mSimCredential, mRealm);
1041     }
1042 
1043     @Override
toString()1044     public String toString() {
1045         StringBuilder builder = new StringBuilder();
1046         builder.append("Realm: ").append(mRealm).append("\n");
1047         builder.append("CreationTime: ").append(mCreationTimeInMillis != Long.MIN_VALUE
1048                 ? new Date(mCreationTimeInMillis) : "Not specified").append("\n");
1049         builder.append("ExpirationTime: ").append(mExpirationTimeInMillis != Long.MIN_VALUE
1050                 ? new Date(mExpirationTimeInMillis) : "Not specified").append("\n");
1051         builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n");
1052         if (mUserCredential != null) {
1053             builder.append("UserCredential Begin ---\n");
1054             builder.append(mUserCredential);
1055             builder.append("UserCredential End ---\n");
1056         }
1057         if (mCertCredential != null) {
1058             builder.append("CertificateCredential Begin ---\n");
1059             builder.append(mCertCredential);
1060             builder.append("CertificateCredential End ---\n");
1061         }
1062         if (mSimCredential != null) {
1063             builder.append("SIMCredential Begin ---\n");
1064             builder.append(mSimCredential);
1065             builder.append("SIMCredential End ---\n");
1066         }
1067         return builder.toString();
1068     }
1069 
1070     /**
1071      * Validate the configuration data.
1072      *
1073      * @return true on success or false on failure
1074      * @hide
1075      */
validate()1076     public boolean validate() {
1077         if (TextUtils.isEmpty(mRealm)) {
1078             Log.d(TAG, "Missing realm");
1079             return false;
1080         }
1081         if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
1082             Log.d(TAG, "realm exceeding maximum length: "
1083                     + mRealm.getBytes(StandardCharsets.UTF_8).length);
1084             return false;
1085         }
1086 
1087         // Verify the credential.
1088         if (mUserCredential != null) {
1089             if (!verifyUserCredential()) {
1090                 return false;
1091             }
1092         } else if (mCertCredential != null) {
1093             if (!verifyCertCredential()) {
1094                 return false;
1095             }
1096         } else if (mSimCredential != null) {
1097             if (!verifySimCredential()) {
1098                 return false;
1099             }
1100         } else {
1101             Log.d(TAG, "Missing required credential");
1102             return false;
1103         }
1104 
1105         return true;
1106     }
1107 
1108     public static final @android.annotation.NonNull Creator<Credential> CREATOR =
1109         new Creator<Credential>() {
1110             @Override
1111             public Credential createFromParcel(Parcel in) {
1112                 Credential credential = new Credential();
1113                 credential.setCreationTimeInMillis(in.readLong());
1114                 credential.setExpirationTimeInMillis(in.readLong());
1115                 credential.setRealm(in.readString());
1116                 credential.setCheckAaaServerCertStatus(in.readInt() != 0);
1117                 credential.setUserCredential(in.readParcelable(null));
1118                 credential.setCertCredential(in.readParcelable(null));
1119                 credential.setSimCredential(in.readParcelable(null));
1120                 credential.setCaCertificates(ParcelUtil.readCertificates(in));
1121                 credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
1122                 credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
1123                 return credential;
1124             }
1125 
1126             @Override
1127             public Credential[] newArray(int size) {
1128                 return new Credential[size];
1129             }
1130         };
1131 
1132     /**
1133      * Verify user credential.
1134      * If no CA certificate is provided, then the system uses the CAs in the trust store.
1135      *
1136      * @return true if user credential is valid, false otherwise.
1137      */
verifyUserCredential()1138     private boolean verifyUserCredential() {
1139         if (mUserCredential == null) {
1140             Log.d(TAG, "Missing user credential");
1141             return false;
1142         }
1143         if (mCertCredential != null || mSimCredential != null) {
1144             Log.d(TAG, "Contained more than one type of credential");
1145             return false;
1146         }
1147         if (!mUserCredential.validate()) {
1148             return false;
1149         }
1150 
1151         return true;
1152     }
1153 
1154     /**
1155      * Verify certificate credential, which is used for EAP-TLS.  This will verify
1156      * that the necessary client key and certificates are provided.
1157      * If no CA certificate is provided, then the system uses the CAs in the trust store.
1158      *
1159      * @return true if certificate credential is valid, false otherwise.
1160      */
verifyCertCredential()1161     private boolean verifyCertCredential() {
1162         if (mCertCredential == null) {
1163             Log.d(TAG, "Missing certificate credential");
1164             return false;
1165         }
1166         if (mUserCredential != null || mSimCredential != null) {
1167             Log.d(TAG, "Contained more than one type of credential");
1168             return false;
1169         }
1170 
1171         if (!mCertCredential.validate()) {
1172             return false;
1173         }
1174 
1175         if (mClientPrivateKey == null) {
1176             Log.d(TAG, "Missing client private key for certificate credential");
1177             return false;
1178         }
1179         try {
1180             // Verify SHA-256 fingerprint for client certificate.
1181             if (!verifySha256Fingerprint(mClientCertificateChain,
1182                     mCertCredential.getCertSha256Fingerprint())) {
1183                 Log.d(TAG, "SHA-256 fingerprint mismatch");
1184                 return false;
1185             }
1186         } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
1187             Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage());
1188             return false;
1189         }
1190 
1191         return true;
1192     }
1193 
1194     /**
1195      * Verify SIM credential.
1196      *
1197      * @return true if SIM credential is valid, false otherwise.
1198      */
verifySimCredential()1199     private boolean verifySimCredential() {
1200         if (mSimCredential == null) {
1201             Log.d(TAG, "Missing SIM credential");
1202             return false;
1203         }
1204         if (mUserCredential != null || mCertCredential != null) {
1205             Log.d(TAG, "Contained more than one type of credential");
1206             return false;
1207         }
1208         return mSimCredential.validate();
1209     }
1210 
isPrivateKeyEquals(PrivateKey key1, PrivateKey key2)1211     private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) {
1212         if (key1 == null && key2 == null) {
1213             return true;
1214         }
1215 
1216         /* Return false if only one of them is null */
1217         if (key1 == null || key2 == null) {
1218             return false;
1219         }
1220 
1221         return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) &&
1222                 Arrays.equals(key1.getEncoded(), key2.getEncoded());
1223     }
1224 
1225     /**
1226      * Verify two X.509 certificates are identical.
1227      *
1228      * @param cert1 a certificate to compare
1229      * @param cert2 a certificate to compare
1230      * @return {@code true} if given certificates are the same each other, {@code false} otherwise.
1231      * @hide
1232      */
isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2)1233     public static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) {
1234         if (cert1 == null && cert2 == null) {
1235             return true;
1236         }
1237 
1238         /* Return false if only one of them is null */
1239         if (cert1 == null || cert2 == null) {
1240             return false;
1241         }
1242 
1243         boolean result = false;
1244         try {
1245             result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded());
1246         } catch (CertificateEncodingException e) {
1247             /* empty, return false. */
1248         }
1249         return result;
1250     }
1251 
isX509CertificatesEquals(X509Certificate[] certs1, X509Certificate[] certs2)1252     private static boolean isX509CertificatesEquals(X509Certificate[] certs1,
1253                                                     X509Certificate[] certs2) {
1254         if (certs1 == null && certs2 == null) {
1255             return true;
1256         }
1257 
1258         /* Return false if only one of them is null */
1259         if (certs1 == null || certs2 == null) {
1260             return false;
1261         }
1262 
1263         if (certs1.length != certs2.length) {
1264             return false;
1265         }
1266 
1267         for (int i = 0; i < certs1.length; i++) {
1268             if (!isX509CertificateEquals(certs1[i], certs2[i])) {
1269                 return false;
1270             }
1271         }
1272 
1273         return true;
1274     }
1275 
1276     /**
1277      * Verify that the digest for a certificate in the certificate chain matches expected
1278      * fingerprint.  The certificate that matches the fingerprint is the client certificate.
1279      *
1280      * @param certChain Chain of certificates
1281      * @param expectedFingerprint The expected SHA-256 digest of the client certificate
1282      * @return true if the certificate chain contains a matching certificate, false otherwise
1283      * @throws NoSuchAlgorithmException
1284      * @throws CertificateEncodingException
1285      */
verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint)1286     private static boolean verifySha256Fingerprint(X509Certificate[] certChain,
1287                                                    byte[] expectedFingerprint)
1288             throws NoSuchAlgorithmException, CertificateEncodingException {
1289         if (certChain == null) {
1290             return false;
1291         }
1292         MessageDigest digester = MessageDigest.getInstance("SHA-256");
1293         for (X509Certificate certificate : certChain) {
1294             digester.reset();
1295             byte[] fingerprint = digester.digest(certificate.getEncoded());
1296             if (Arrays.equals(expectedFingerprint, fingerprint)) {
1297                 return true;
1298             }
1299         }
1300         return false;
1301     }
1302 }
1303