Home | History | Annotate | Download | only in pps
      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      */
     65     public void setCreationTimeInMillis(long creationTimeInMillis) {
     66         mCreationTimeInMillis = creationTimeInMillis;
     67     }
     68     /**
     69      * @hide
     70      */
     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      */
     84     public void setExpirationTimeInMillis(long expirationTimeInMillis) {
     85         mExpirationTimeInMillis = expirationTimeInMillis;
     86     }
     87     /**
     88      * @hide
     89      */
     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      */
    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      */
    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      */
    126     public void setCheckAaaServerCertStatus(boolean checkAaaServerCertStatus) {
    127         mCheckAaaServerCertStatus = checkAaaServerCertStatus;
    128     }
    129     /**
    130      * @hide
    131      */
    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          */
    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          */
    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          */
    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          */
    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          */
    220         public void setMachineManaged(boolean machineManaged) {
    221             mMachineManaged = machineManaged;
    222         }
    223         /**
    224          * @hide
    225          */
    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          */
    237         public void setSoftTokenApp(String softTokenApp) {
    238             mSoftTokenApp = softTokenApp;
    239         }
    240         /**
    241          * @hide
    242          */
    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          */
    254         public void setAbleToShare(boolean ableToShare) {
    255             mAbleToShare = ableToShare;
    256         }
    257         /**
    258          * @hide
    259          */
    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          */
    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          */
    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          */
    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          */
    311         public String getNonEapInnerMethod() {
    312             return mNonEapInnerMethod;
    313         }
    314 
    315         /**
    316          * Constructor for creating UserCredential with default values.
    317          */
    318         public UserCredential() {}
    319 
    320         /**
    321          * Copy constructor.
    322          *
    323          * @param source The source to copy from
    324          */
    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
    338         public int describeContents() {
    339             return 0;
    340         }
    341 
    342         @Override
    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
    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
    373         public int hashCode() {
    374             return Objects.hash(mUsername, mPassword, mMachineManaged, mSoftTokenApp,
    375                     mAbleToShare, mEapType, mNonEapInnerMethod);
    376         }
    377 
    378         @Override
    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          */
    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      */
    458     public void setUserCredential(UserCredential userCredential) {
    459         mUserCredential = userCredential;
    460     }
    461     /**
    462      * Get the user credential information.
    463      *
    464      * @return user credential information
    465      */
    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          */
    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          */
    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          */
    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          */
    524         public byte[] getCertSha256Fingerprint() {
    525             return mCertSha256Fingerprint;
    526         }
    527 
    528         /**
    529          * Constructor for creating CertificateCredential with default values.
    530          */
    531         public CertificateCredential() {}
    532 
    533         /**
    534          * Copy constructor.
    535          *
    536          * @param source The source to copy from
    537          */
    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
    549         public int describeContents() {
    550             return 0;
    551         }
    552 
    553         @Override
    554         public void writeToParcel(Parcel dest, int flags) {
    555             dest.writeString(mCertType);
    556             dest.writeByteArray(mCertSha256Fingerprint);
    557         }
    558 
    559         @Override
    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
    574         public int hashCode() {
    575             return Objects.hash(mCertType, mCertSha256Fingerprint);
    576         }
    577 
    578         @Override
    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          */
    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      */
    624     public void setCertCredential(CertificateCredential certCredential) {
    625         mCertCredential = certCredential;
    626     }
    627     /**
    628      * Get the certificate credential information.
    629      *
    630      * @return certificate credential information
    631      */
    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          */
    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          */
    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          */
    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          */
    693         public int getEapType() {
    694             return mEapType;
    695         }
    696 
    697         /**
    698          * Constructor for creating SimCredential with default values.
    699          */
    700         public SimCredential() {}
    701 
    702         /**
    703          * Copy constructor
    704          *
    705          * @param source The source to copy from
    706          */
    707         public SimCredential(SimCredential source) {
    708             if (source != null) {
    709                 mImsi = source.mImsi;
    710                 mEapType = source.mEapType;
    711             }
    712         }
    713 
    714         @Override
    715         public int describeContents() {
    716             return 0;
    717         }
    718 
    719         @Override
    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
    734         public int hashCode() {
    735             return Objects.hash(mImsi, mEapType);
    736         }
    737 
    738         @Override
    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
    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          */
    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          */
    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      */
    832     public void setSimCredential(SimCredential simCredential) {
    833         mSimCredential = simCredential;
    834     }
    835     /**
    836      * Get the SIM credential information.
    837      *
    838      * @return SIM credential information
    839      */
    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      */
    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      */
    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      */
    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      */
    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      */
    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      */
    903     public PrivateKey getClientPrivateKey() {
    904         return mClientPrivateKey;
    905     }
    906 
    907     /**
    908      * Constructor for creating Credential with default values.
    909      */
    910     public Credential() {}
    911 
    912     /**
    913      * Copy constructor.
    914      *
    915      * @param source The source to copy from
    916      */
    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
    942     public int describeContents() {
    943         return 0;
    944     }
    945 
    946     @Override
    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
    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
    986     public int hashCode() {
    987         return Objects.hash(mRealm, mCreationTimeInMillis, mExpirationTimeInMillis,
    988                 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential,
    989                 mCaCertificate, mClientCertificateChain, mClientPrivateKey);
    990     }
    991 
    992     @Override
    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      */
   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      */
   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      */
   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      */
   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 
   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 
   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 
   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      */
   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