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