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             builder.append("IMSI: ").append(mImsi).append("\n");
742             builder.append("EAPType: ").append(mEapType).append("\n");
743             return builder.toString();
744         }
745 
746         @Override
writeToParcel(Parcel dest, int flags)747         public void writeToParcel(Parcel dest, int flags) {
748             dest.writeString(mImsi);
749             dest.writeInt(mEapType);
750         }
751 
752         /**
753          * Validate the configuration data.
754          *
755          * @return true on success or false on failure
756          * @hide
757          */
validate()758         public boolean validate() {
759             // Note: this only validate the format of IMSI string itself.  Additional verification
760             // will be done by WifiService at the time of provisioning to verify against the IMSI
761             // of the SIM card installed in the device.
762             if (!verifyImsi()) {
763                 return false;
764             }
765             if (mEapType != EAPConstants.EAP_SIM && mEapType != EAPConstants.EAP_AKA
766                     && mEapType != EAPConstants.EAP_AKA_PRIME) {
767                 Log.d(TAG, "Invalid EAP Type for SIM credential: " + mEapType);
768                 return false;
769             }
770             return true;
771         }
772 
773         public static final @android.annotation.NonNull Creator<SimCredential> CREATOR =
774             new Creator<SimCredential>() {
775                 @Override
776                 public SimCredential createFromParcel(Parcel in) {
777                     SimCredential simCredential = new SimCredential();
778                     simCredential.setImsi(in.readString());
779                     simCredential.setEapType(in.readInt());
780                     return simCredential;
781                 }
782 
783                 @Override
784                 public SimCredential[] newArray(int size) {
785                     return new SimCredential[size];
786                 }
787             };
788 
789         /**
790          * Verify the IMSI (International Mobile Subscriber Identity) string.  The string
791          * should contain zero or more numeric digits, and might ends with a "*" for prefix
792          * matching.
793          *
794          * @return true if IMSI is valid, false otherwise.
795          */
verifyImsi()796         private boolean verifyImsi() {
797             if (TextUtils.isEmpty(mImsi)) {
798                 Log.d(TAG, "Missing IMSI");
799                 return false;
800             }
801             if (mImsi.length() > MAX_IMSI_LENGTH) {
802                 Log.d(TAG, "IMSI exceeding maximum length: " + mImsi.length());
803                 return false;
804             }
805 
806             // Locate the first non-digit character.
807             int nonDigit;
808             char stopChar = '\0';
809             for (nonDigit = 0; nonDigit < mImsi.length(); nonDigit++) {
810                 stopChar = mImsi.charAt(nonDigit);
811                 if (stopChar < '0' || stopChar > '9') {
812                     break;
813                 }
814             }
815 
816             if (nonDigit == mImsi.length()) {
817                 return true;
818             }
819             else if (nonDigit == mImsi.length()-1 && stopChar == '*') {
820                 // Prefix matching.
821                 return true;
822             }
823             return false;
824         }
825     }
826     private SimCredential mSimCredential = null;
827     /**
828      * Set the SIM credential information.
829      *
830      * @param simCredential The SIM credential to set to
831      */
setSimCredential(SimCredential simCredential)832     public void setSimCredential(SimCredential simCredential) {
833         mSimCredential = simCredential;
834     }
835     /**
836      * Get the SIM credential information.
837      *
838      * @return SIM credential information
839      */
getSimCredential()840     public SimCredential getSimCredential() {
841         return mSimCredential;
842     }
843 
844     /**
845      * CA (Certificate Authority) X509 certificates.
846      */
847     private X509Certificate[] mCaCertificates = null;
848 
849     /**
850      * Set the CA (Certification Authority) certificate associated with this credential.
851      *
852      * @param caCertificate The CA certificate to set to
853      */
setCaCertificate(X509Certificate caCertificate)854     public void setCaCertificate(X509Certificate caCertificate) {
855         mCaCertificates = null;
856         if (caCertificate != null) {
857             mCaCertificates = new X509Certificate[] {caCertificate};
858         }
859     }
860 
861     /**
862      * Set the CA (Certification Authority) certificates associated with this credential.
863      *
864      * @param caCertificates The list of CA certificates to set to
865      * @hide
866      */
setCaCertificates(X509Certificate[] caCertificates)867     public void setCaCertificates(X509Certificate[] caCertificates) {
868         mCaCertificates = caCertificates;
869     }
870 
871     /**
872      * Get the CA (Certification Authority) certificate associated with this credential.
873      *
874      * @return CA certificate associated with this credential, {@code null} if certificate is not
875      * set or certificate is more than one.
876      */
getCaCertificate()877     public X509Certificate getCaCertificate() {
878         return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0];
879     }
880 
881     /**
882      * Get the CA (Certification Authority) certificates associated with this credential.
883      *
884      * @return The list of CA certificates associated with this credential
885      * @hide
886      */
getCaCertificates()887     public X509Certificate[] getCaCertificates() {
888         return mCaCertificates;
889     }
890 
891     /**
892      * Client side X509 certificate chain.
893      */
894     private X509Certificate[] mClientCertificateChain = null;
895     /**
896      * Set the client certificate chain associated with this credential.
897      *
898      * @param certificateChain The client certificate chain to set to
899      */
setClientCertificateChain(X509Certificate[] certificateChain)900     public void setClientCertificateChain(X509Certificate[] certificateChain) {
901         mClientCertificateChain = certificateChain;
902     }
903     /**
904      * Get the client certificate chain associated with this credential.
905      *
906      * @return client certificate chain associated with this credential
907      */
getClientCertificateChain()908     public X509Certificate[] getClientCertificateChain() {
909         return mClientCertificateChain;
910     }
911 
912     /**
913      * Client side private key.
914      */
915     private PrivateKey mClientPrivateKey = null;
916     /**
917      * Set the client private key associated with this credential.
918      *
919      * @param clientPrivateKey the client private key to set to
920      */
setClientPrivateKey(PrivateKey clientPrivateKey)921     public void setClientPrivateKey(PrivateKey clientPrivateKey) {
922         mClientPrivateKey = clientPrivateKey;
923     }
924     /**
925      * Get the client private key associated with this credential.
926      *
927      * @return client private key associated with this credential.
928      */
getClientPrivateKey()929     public PrivateKey getClientPrivateKey() {
930         return mClientPrivateKey;
931     }
932 
933     /**
934      * Constructor for creating Credential with default values.
935      */
Credential()936     public Credential() {}
937 
938     /**
939      * Copy constructor.
940      *
941      * @param source The source to copy from
942      */
Credential(Credential source)943     public Credential(Credential source) {
944         if (source != null) {
945             mCreationTimeInMillis = source.mCreationTimeInMillis;
946             mExpirationTimeInMillis = source.mExpirationTimeInMillis;
947             mRealm = source.mRealm;
948             mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus;
949             if (source.mUserCredential != null) {
950                 mUserCredential = new UserCredential(source.mUserCredential);
951             }
952             if (source.mCertCredential != null) {
953                 mCertCredential = new CertificateCredential(source.mCertCredential);
954             }
955             if (source.mSimCredential != null) {
956                 mSimCredential = new SimCredential(source.mSimCredential);
957             }
958             if (source.mClientCertificateChain != null) {
959                 mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
960                                                         source.mClientCertificateChain.length);
961             }
962             if (source.mCaCertificates != null) {
963                 mCaCertificates = Arrays.copyOf(source.mCaCertificates,
964                         source.mCaCertificates.length);
965             }
966 
967             mClientPrivateKey = source.mClientPrivateKey;
968         }
969     }
970 
971     @Override
describeContents()972     public int describeContents() {
973         return 0;
974     }
975 
976     @Override
writeToParcel(Parcel dest, int flags)977     public void writeToParcel(Parcel dest, int flags) {
978         dest.writeLong(mCreationTimeInMillis);
979         dest.writeLong(mExpirationTimeInMillis);
980         dest.writeString(mRealm);
981         dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0);
982         dest.writeParcelable(mUserCredential, flags);
983         dest.writeParcelable(mCertCredential, flags);
984         dest.writeParcelable(mSimCredential, flags);
985         ParcelUtil.writeCertificates(dest, mCaCertificates);
986         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
987         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
988     }
989 
990     @Override
equals(Object thatObject)991     public boolean equals(Object thatObject) {
992         if (this == thatObject) {
993             return true;
994         }
995         if (!(thatObject instanceof Credential)) {
996             return false;
997         }
998 
999         Credential that = (Credential) thatObject;
1000         return TextUtils.equals(mRealm, that.mRealm)
1001                 && mCreationTimeInMillis == that.mCreationTimeInMillis
1002                 && mExpirationTimeInMillis == that.mExpirationTimeInMillis
1003                 && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus
1004                 && (mUserCredential == null ? that.mUserCredential == null
1005                     : mUserCredential.equals(that.mUserCredential))
1006                 && (mCertCredential == null ? that.mCertCredential == null
1007                     : mCertCredential.equals(that.mCertCredential))
1008                 && (mSimCredential == null ? that.mSimCredential == null
1009                     : mSimCredential.equals(that.mSimCredential))
1010                 && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates)
1011                 && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
1012                 && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey);
1013     }
1014 
1015     @Override
hashCode()1016     public int hashCode() {
1017         return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm,
1018                 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
1019                 mClientPrivateKey, Arrays.hashCode(mCaCertificates),
1020                 Arrays.hashCode(mClientCertificateChain));
1021     }
1022 
1023     @Override
toString()1024     public String toString() {
1025         StringBuilder builder = new StringBuilder();
1026         builder.append("Realm: ").append(mRealm).append("\n");
1027         builder.append("CreationTime: ").append(mCreationTimeInMillis != Long.MIN_VALUE
1028                 ? new Date(mCreationTimeInMillis) : "Not specified").append("\n");
1029         builder.append("ExpirationTime: ").append(mExpirationTimeInMillis != Long.MIN_VALUE
1030                 ? new Date(mExpirationTimeInMillis) : "Not specified").append("\n");
1031         builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n");
1032         if (mUserCredential != null) {
1033             builder.append("UserCredential Begin ---\n");
1034             builder.append(mUserCredential);
1035             builder.append("UserCredential End ---\n");
1036         }
1037         if (mCertCredential != null) {
1038             builder.append("CertificateCredential Begin ---\n");
1039             builder.append(mCertCredential);
1040             builder.append("CertificateCredential End ---\n");
1041         }
1042         if (mSimCredential != null) {
1043             builder.append("SIMCredential Begin ---\n");
1044             builder.append(mSimCredential);
1045             builder.append("SIMCredential End ---\n");
1046         }
1047         return builder.toString();
1048     }
1049 
1050     /**
1051      * Validate the configuration data.
1052      *
1053      * @param isR1 {@code true} if the configuration is for R1
1054      * @return true on success or false on failure
1055      * @hide
1056      */
validate(boolean isR1)1057     public boolean validate(boolean isR1) {
1058         if (TextUtils.isEmpty(mRealm)) {
1059             Log.d(TAG, "Missing realm");
1060             return false;
1061         }
1062         if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
1063             Log.d(TAG, "realm exceeding maximum length: "
1064                     + mRealm.getBytes(StandardCharsets.UTF_8).length);
1065             return false;
1066         }
1067 
1068         // Verify the credential.
1069         if (mUserCredential != null) {
1070             if (!verifyUserCredential(isR1)) {
1071                 return false;
1072             }
1073         } else if (mCertCredential != null) {
1074             if (!verifyCertCredential(isR1)) {
1075                 return false;
1076             }
1077         } else if (mSimCredential != null) {
1078             if (!verifySimCredential()) {
1079                 return false;
1080             }
1081         } else {
1082             Log.d(TAG, "Missing required credential");
1083             return false;
1084         }
1085 
1086         return true;
1087     }
1088 
1089     public static final @android.annotation.NonNull Creator<Credential> CREATOR =
1090         new Creator<Credential>() {
1091             @Override
1092             public Credential createFromParcel(Parcel in) {
1093                 Credential credential = new Credential();
1094                 credential.setCreationTimeInMillis(in.readLong());
1095                 credential.setExpirationTimeInMillis(in.readLong());
1096                 credential.setRealm(in.readString());
1097                 credential.setCheckAaaServerCertStatus(in.readInt() != 0);
1098                 credential.setUserCredential(in.readParcelable(null));
1099                 credential.setCertCredential(in.readParcelable(null));
1100                 credential.setSimCredential(in.readParcelable(null));
1101                 credential.setCaCertificates(ParcelUtil.readCertificates(in));
1102                 credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
1103                 credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
1104                 return credential;
1105             }
1106 
1107             @Override
1108             public Credential[] newArray(int size) {
1109                 return new Credential[size];
1110             }
1111         };
1112 
1113     /**
1114      * Verify user credential.
1115      *
1116      * @param isR1 {@code true} if credential is for R1
1117      * @return true if user credential is valid, false otherwise.
1118      */
verifyUserCredential(boolean isR1)1119     private boolean verifyUserCredential(boolean isR1) {
1120         if (mUserCredential == null) {
1121             Log.d(TAG, "Missing user credential");
1122             return false;
1123         }
1124         if (mCertCredential != null || mSimCredential != null) {
1125             Log.d(TAG, "Contained more than one type of credential");
1126             return false;
1127         }
1128         if (!mUserCredential.validate()) {
1129             return false;
1130         }
1131 
1132         // CA certificate is required for R1 Passpoint profile.
1133         // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
1134         if (isR1 && mCaCertificates == null) {
1135             Log.d(TAG, "Missing CA Certificate for user credential");
1136             return false;
1137         }
1138         return true;
1139     }
1140 
1141     /**
1142      * Verify certificate credential, which is used for EAP-TLS.  This will verify
1143      * that the necessary client key and certificates are provided.
1144      *
1145      * @param isR1 {@code true} if credential is for R1
1146      * @return true if certificate credential is valid, false otherwise.
1147      */
verifyCertCredential(boolean isR1)1148     private boolean verifyCertCredential(boolean isR1) {
1149         if (mCertCredential == null) {
1150             Log.d(TAG, "Missing certificate credential");
1151             return false;
1152         }
1153         if (mUserCredential != null || mSimCredential != null) {
1154             Log.d(TAG, "Contained more than one type of credential");
1155             return false;
1156         }
1157 
1158         if (!mCertCredential.validate()) {
1159             return false;
1160         }
1161 
1162         // Verify required key and certificates for certificate credential.
1163         // CA certificate is required for R1 Passpoint profile.
1164         // For R2, it is downloaded using cert URL provided in PPS MO after validation completes.
1165         if (isR1 && mCaCertificates == null) {
1166             Log.d(TAG, "Missing CA Certificate for certificate credential");
1167             return false;
1168         }
1169         if (mClientPrivateKey == null) {
1170             Log.d(TAG, "Missing client private key for certificate credential");
1171             return false;
1172         }
1173         try {
1174             // Verify SHA-256 fingerprint for client certificate.
1175             if (!verifySha256Fingerprint(mClientCertificateChain,
1176                     mCertCredential.getCertSha256Fingerprint())) {
1177                 Log.d(TAG, "SHA-256 fingerprint mismatch");
1178                 return false;
1179             }
1180         } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
1181             Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage());
1182             return false;
1183         }
1184 
1185         return true;
1186     }
1187 
1188     /**
1189      * Verify SIM credential.
1190      *
1191      * @return true if SIM credential is valid, false otherwise.
1192      */
verifySimCredential()1193     private boolean verifySimCredential() {
1194         if (mSimCredential == null) {
1195             Log.d(TAG, "Missing SIM credential");
1196             return false;
1197         }
1198         if (mUserCredential != null || mCertCredential != null) {
1199             Log.d(TAG, "Contained more than one type of credential");
1200             return false;
1201         }
1202         return mSimCredential.validate();
1203     }
1204 
isPrivateKeyEquals(PrivateKey key1, PrivateKey key2)1205     private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) {
1206         if (key1 == null && key2 == null) {
1207             return true;
1208         }
1209 
1210         /* Return false if only one of them is null */
1211         if (key1 == null || key2 == null) {
1212             return false;
1213         }
1214 
1215         return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) &&
1216                 Arrays.equals(key1.getEncoded(), key2.getEncoded());
1217     }
1218 
1219     /**
1220      * Verify two X.509 certificates are identical.
1221      *
1222      * @param cert1 a certificate to compare
1223      * @param cert2 a certificate to compare
1224      * @return {@code true} if given certificates are the same each other, {@code false} otherwise.
1225      * @hide
1226      */
isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2)1227     public static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) {
1228         if (cert1 == null && cert2 == null) {
1229             return true;
1230         }
1231 
1232         /* Return false if only one of them is null */
1233         if (cert1 == null || cert2 == null) {
1234             return false;
1235         }
1236 
1237         boolean result = false;
1238         try {
1239             result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded());
1240         } catch (CertificateEncodingException e) {
1241             /* empty, return false. */
1242         }
1243         return result;
1244     }
1245 
isX509CertificatesEquals(X509Certificate[] certs1, X509Certificate[] certs2)1246     private static boolean isX509CertificatesEquals(X509Certificate[] certs1,
1247                                                     X509Certificate[] certs2) {
1248         if (certs1 == null && certs2 == null) {
1249             return true;
1250         }
1251 
1252         /* Return false if only one of them is null */
1253         if (certs1 == null || certs2 == null) {
1254             return false;
1255         }
1256 
1257         if (certs1.length != certs2.length) {
1258             return false;
1259         }
1260 
1261         for (int i = 0; i < certs1.length; i++) {
1262             if (!isX509CertificateEquals(certs1[i], certs2[i])) {
1263                 return false;
1264             }
1265         }
1266 
1267         return true;
1268     }
1269 
1270     /**
1271      * Verify that the digest for a certificate in the certificate chain matches expected
1272      * fingerprint.  The certificate that matches the fingerprint is the client certificate.
1273      *
1274      * @param certChain Chain of certificates
1275      * @param expectedFingerprint The expected SHA-256 digest of the client certificate
1276      * @return true if the certificate chain contains a matching certificate, false otherwise
1277      * @throws NoSuchAlgorithmException
1278      * @throws CertificateEncodingException
1279      */
verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint)1280     private static boolean verifySha256Fingerprint(X509Certificate[] certChain,
1281                                                    byte[] expectedFingerprint)
1282             throws NoSuchAlgorithmException, CertificateEncodingException {
1283         if (certChain == null) {
1284             return false;
1285         }
1286         MessageDigest digester = MessageDigest.getInstance("SHA-256");
1287         for (X509Certificate certificate : certChain) {
1288             digester.reset();
1289             byte[] fingerprint = digester.digest(certificate.getEncoded());
1290             if (Arrays.equals(expectedFingerprint, fingerprint)) {
1291                 return true;
1292             }
1293         }
1294         return false;
1295     }
1296 }
1297