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