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