• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /**
2  * Copyright (c) 2016, The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package android.net.wifi.hotspot2.pps;
18 
19 import android.net.wifi.EAPConstants;
20 import android.net.wifi.ParcelUtil;
21 import android.os.Parcelable;
22 import android.os.Parcel;
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 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, 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 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 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 certificate.
846      */
847     private X509Certificate mCaCertificate = null;
848     /**
849      * Set the CA (Certification Authority) certificate associated with this credential.
850      *
851      * @param caCertificate The CA certificate to set to
852      */
setCaCertificate(X509Certificate caCertificate)853     public void setCaCertificate(X509Certificate caCertificate) {
854         mCaCertificate = caCertificate;
855     }
856     /**
857      * Get the CA (Certification Authority) certificate associated with this credential.
858      *
859      * @return CA certificate associated with this credential
860      */
getCaCertificate()861     public X509Certificate getCaCertificate() {
862         return mCaCertificate;
863     }
864 
865     /**
866      * Client side X509 certificate chain.
867      */
868     private X509Certificate[] mClientCertificateChain = null;
869     /**
870      * Set the client certificate chain associated with this credential.
871      *
872      * @param certificateChain The client certificate chain to set to
873      */
setClientCertificateChain(X509Certificate[] certificateChain)874     public void setClientCertificateChain(X509Certificate[] certificateChain) {
875         mClientCertificateChain = certificateChain;
876     }
877     /**
878      * Get the client certificate chain associated with this credential.
879      *
880      * @return client certificate chain associated with this credential
881      */
getClientCertificateChain()882     public X509Certificate[] getClientCertificateChain() {
883         return mClientCertificateChain;
884     }
885 
886     /**
887      * Client side private key.
888      */
889     private PrivateKey mClientPrivateKey = null;
890     /**
891      * Set the client private key associated with this credential.
892      *
893      * @param clientPrivateKey the client private key to set to
894      */
setClientPrivateKey(PrivateKey clientPrivateKey)895     public void setClientPrivateKey(PrivateKey clientPrivateKey) {
896         mClientPrivateKey = clientPrivateKey;
897     }
898     /**
899      * Get the client private key associated with this credential.
900      *
901      * @return client private key associated with this credential.
902      */
getClientPrivateKey()903     public PrivateKey getClientPrivateKey() {
904         return mClientPrivateKey;
905     }
906 
907     /**
908      * Constructor for creating Credential with default values.
909      */
Credential()910     public Credential() {}
911 
912     /**
913      * Copy constructor.
914      *
915      * @param source The source to copy from
916      */
Credential(Credential source)917     public Credential(Credential source) {
918         if (source != null) {
919             mCreationTimeInMillis = source.mCreationTimeInMillis;
920             mExpirationTimeInMillis = source.mExpirationTimeInMillis;
921             mRealm = source.mRealm;
922             mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus;
923             if (source.mUserCredential != null) {
924                 mUserCredential = new UserCredential(source.mUserCredential);
925             }
926             if (source.mCertCredential != null) {
927                 mCertCredential = new CertificateCredential(source.mCertCredential);
928             }
929             if (source.mSimCredential != null) {
930                 mSimCredential = new SimCredential(source.mSimCredential);
931             }
932             if (source.mClientCertificateChain != null) {
933                 mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain,
934                                                         source.mClientCertificateChain.length);
935             }
936             mCaCertificate = source.mCaCertificate;
937             mClientPrivateKey = source.mClientPrivateKey;
938         }
939     }
940 
941     @Override
describeContents()942     public int describeContents() {
943         return 0;
944     }
945 
946     @Override
writeToParcel(Parcel dest, int flags)947     public void writeToParcel(Parcel dest, int flags) {
948         dest.writeLong(mCreationTimeInMillis);
949         dest.writeLong(mExpirationTimeInMillis);
950         dest.writeString(mRealm);
951         dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0);
952         dest.writeParcelable(mUserCredential, flags);
953         dest.writeParcelable(mCertCredential, flags);
954         dest.writeParcelable(mSimCredential, flags);
955         ParcelUtil.writeCertificate(dest, mCaCertificate);
956         ParcelUtil.writeCertificates(dest, mClientCertificateChain);
957         ParcelUtil.writePrivateKey(dest, mClientPrivateKey);
958     }
959 
960     @Override
equals(Object thatObject)961     public boolean equals(Object thatObject) {
962         if (this == thatObject) {
963             return true;
964         }
965         if (!(thatObject instanceof Credential)) {
966             return false;
967         }
968 
969         Credential that = (Credential) thatObject;
970         return TextUtils.equals(mRealm, that.mRealm)
971                 && mCreationTimeInMillis == that.mCreationTimeInMillis
972                 && mExpirationTimeInMillis == that.mExpirationTimeInMillis
973                 && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus
974                 && (mUserCredential == null ? that.mUserCredential == null
975                     : mUserCredential.equals(that.mUserCredential))
976                 && (mCertCredential == null ? that.mCertCredential == null
977                     : mCertCredential.equals(that.mCertCredential))
978                 && (mSimCredential == null ? that.mSimCredential == null
979                     : mSimCredential.equals(that.mSimCredential))
980                 && isX509CertificateEquals(mCaCertificate, that.mCaCertificate)
981                 && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain)
982                 && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey);
983     }
984 
985     @Override
hashCode()986     public int hashCode() {
987         return Objects.hash(mRealm, mCreationTimeInMillis, mExpirationTimeInMillis,
988                 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
989                 mCaCertificate, mClientCertificateChain, mClientPrivateKey);
990     }
991 
992     @Override
toString()993     public String toString() {
994         StringBuilder builder = new StringBuilder();
995         builder.append("Realm: ").append(mRealm).append("\n");
996         builder.append("CreationTime: ").append(mCreationTimeInMillis != Long.MIN_VALUE
997                 ? new Date(mCreationTimeInMillis) : "Not specified").append("\n");
998         builder.append("ExpirationTime: ").append(mExpirationTimeInMillis != Long.MIN_VALUE
999                 ? new Date(mExpirationTimeInMillis) : "Not specified").append("\n");
1000         builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n");
1001         if (mUserCredential != null) {
1002             builder.append("UserCredential Begin ---\n");
1003             builder.append(mUserCredential);
1004             builder.append("UserCredential End ---\n");
1005         }
1006         if (mCertCredential != null) {
1007             builder.append("CertificateCredential Begin ---\n");
1008             builder.append(mCertCredential);
1009             builder.append("CertificateCredential End ---\n");
1010         }
1011         if (mSimCredential != null) {
1012             builder.append("SIMCredential Begin ---\n");
1013             builder.append(mSimCredential);
1014             builder.append("SIMCredential End ---\n");
1015         }
1016         return builder.toString();
1017     }
1018 
1019     /**
1020      * Validate the configuration data.
1021      *
1022      * @return true on success or false on failure
1023      * @hide
1024      */
validate()1025     public boolean validate() {
1026         if (TextUtils.isEmpty(mRealm)) {
1027             Log.d(TAG, "Missing realm");
1028             return false;
1029         }
1030         if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
1031             Log.d(TAG, "realm exceeding maximum length: "
1032                     + mRealm.getBytes(StandardCharsets.UTF_8).length);
1033             return false;
1034         }
1035 
1036         // Verify the credential.
1037         if (mUserCredential != null) {
1038             if (!verifyUserCredential()) {
1039                 return false;
1040             }
1041         } else if (mCertCredential != null) {
1042             if (!verifyCertCredential()) {
1043                 return false;
1044             }
1045         } else if (mSimCredential != null) {
1046             if (!verifySimCredential()) {
1047                 return false;
1048             }
1049         } else {
1050             Log.d(TAG, "Missing required credential");
1051             return false;
1052         }
1053 
1054         return true;
1055     }
1056 
1057     public static final Creator<Credential> CREATOR =
1058         new Creator<Credential>() {
1059             @Override
1060             public Credential createFromParcel(Parcel in) {
1061                 Credential credential = new Credential();
1062                 credential.setCreationTimeInMillis(in.readLong());
1063                 credential.setExpirationTimeInMillis(in.readLong());
1064                 credential.setRealm(in.readString());
1065                 credential.setCheckAaaServerCertStatus(in.readInt() != 0);
1066                 credential.setUserCredential(in.readParcelable(null));
1067                 credential.setCertCredential(in.readParcelable(null));
1068                 credential.setSimCredential(in.readParcelable(null));
1069                 credential.setCaCertificate(ParcelUtil.readCertificate(in));
1070                 credential.setClientCertificateChain(ParcelUtil.readCertificates(in));
1071                 credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in));
1072                 return credential;
1073             }
1074 
1075             @Override
1076             public Credential[] newArray(int size) {
1077                 return new Credential[size];
1078             }
1079         };
1080 
1081     /**
1082      * Verify user credential.
1083      *
1084      * @return true if user credential is valid, false otherwise.
1085      */
verifyUserCredential()1086     private boolean verifyUserCredential() {
1087         if (mUserCredential == null) {
1088             Log.d(TAG, "Missing user credential");
1089             return false;
1090         }
1091         if (mCertCredential != null || mSimCredential != null) {
1092             Log.d(TAG, "Contained more than one type of credential");
1093             return false;
1094         }
1095         if (!mUserCredential.validate()) {
1096             return false;
1097         }
1098         if (mCaCertificate == null) {
1099             Log.d(TAG, "Missing CA Certificate for user credential");
1100             return false;
1101         }
1102         return true;
1103     }
1104 
1105     /**
1106      * Verify certificate credential, which is used for EAP-TLS.  This will verify
1107      * that the necessary client key and certificates are provided.
1108      *
1109      * @return true if certificate credential is valid, false otherwise.
1110      */
verifyCertCredential()1111     private boolean verifyCertCredential() {
1112         if (mCertCredential == null) {
1113             Log.d(TAG, "Missing certificate credential");
1114             return false;
1115         }
1116         if (mUserCredential != null || mSimCredential != null) {
1117             Log.d(TAG, "Contained more than one type of credential");
1118             return false;
1119         }
1120 
1121         if (!mCertCredential.validate()) {
1122             return false;
1123         }
1124 
1125         // Verify required key and certificates for certificate credential.
1126         if (mCaCertificate == null) {
1127             Log.d(TAG, "Missing CA Certificate for certificate credential");
1128             return false;
1129         }
1130         if (mClientPrivateKey == null) {
1131             Log.d(TAG, "Missing client private key for certificate credential");
1132             return false;
1133         }
1134         try {
1135             // Verify SHA-256 fingerprint for client certificate.
1136             if (!verifySha256Fingerprint(mClientCertificateChain,
1137                     mCertCredential.getCertSha256Fingerprint())) {
1138                 Log.d(TAG, "SHA-256 fingerprint mismatch");
1139                 return false;
1140             }
1141         } catch (NoSuchAlgorithmException | CertificateEncodingException e) {
1142             Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage());
1143             return false;
1144         }
1145 
1146         return true;
1147     }
1148 
1149     /**
1150      * Verify SIM credential.
1151      *
1152      * @return true if SIM credential is valid, false otherwise.
1153      */
verifySimCredential()1154     private boolean verifySimCredential() {
1155         if (mSimCredential == null) {
1156             Log.d(TAG, "Missing SIM credential");
1157             return false;
1158         }
1159         if (mUserCredential != null || mCertCredential != null) {
1160             Log.d(TAG, "Contained more than one type of credential");
1161             return false;
1162         }
1163         return mSimCredential.validate();
1164     }
1165 
isPrivateKeyEquals(PrivateKey key1, PrivateKey key2)1166     private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) {
1167         if (key1 == null && key2 == null) {
1168             return true;
1169         }
1170 
1171         /* Return false if only one of them is null */
1172         if (key1 == null || key2 == null) {
1173             return false;
1174         }
1175 
1176         return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) &&
1177                 Arrays.equals(key1.getEncoded(), key2.getEncoded());
1178     }
1179 
isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2)1180     private static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) {
1181         if (cert1 == null && cert2 == null) {
1182             return true;
1183         }
1184 
1185         /* Return false if only one of them is null */
1186         if (cert1 == null || cert2 == null) {
1187             return false;
1188         }
1189 
1190         boolean result = false;
1191         try {
1192             result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded());
1193         } catch (CertificateEncodingException e) {
1194             /* empty, return false. */
1195         }
1196         return result;
1197     }
1198 
isX509CertificatesEquals(X509Certificate[] certs1, X509Certificate[] certs2)1199     private static boolean isX509CertificatesEquals(X509Certificate[] certs1,
1200                                                     X509Certificate[] certs2) {
1201         if (certs1 == null && certs2 == null) {
1202             return true;
1203         }
1204 
1205         /* Return false if only one of them is null */
1206         if (certs1 == null || certs2 == null) {
1207             return false;
1208         }
1209 
1210         if (certs1.length != certs2.length) {
1211             return false;
1212         }
1213 
1214         for (int i = 0; i < certs1.length; i++) {
1215             if (!isX509CertificateEquals(certs1[i], certs2[i])) {
1216                 return false;
1217             }
1218         }
1219 
1220         return true;
1221     }
1222 
1223     /**
1224      * Verify that the digest for a certificate in the certificate chain matches expected
1225      * fingerprint.  The certificate that matches the fingerprint is the client certificate.
1226      *
1227      * @param certChain Chain of certificates
1228      * @param expectedFingerprint The expected SHA-256 digest of the client certificate
1229      * @return true if the certificate chain contains a matching certificate, false otherwise
1230      * @throws NoSuchAlgorithmException
1231      * @throws CertificateEncodingException
1232      */
verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint)1233     private static boolean verifySha256Fingerprint(X509Certificate[] certChain,
1234                                                    byte[] expectedFingerprint)
1235             throws NoSuchAlgorithmException, CertificateEncodingException {
1236         if (certChain == null) {
1237             return false;
1238         }
1239         MessageDigest digester = MessageDigest.getInstance("SHA-256");
1240         for (X509Certificate certificate : certChain) {
1241             digester.reset();
1242             byte[] fingerprint = digester.digest(certificate.getEncoded());
1243             if (Arrays.equals(expectedFingerprint, fingerprint)) {
1244                 return true;
1245             }
1246         }
1247         return false;
1248     }
1249 }
1250