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