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 private UserCredential mUserCredential = null; 453 /** 454 * Set the user credential information. 455 * 456 * @param userCredential The user credential to set to 457 */ setUserCredential(UserCredential userCredential)458 public void setUserCredential(UserCredential userCredential) { 459 mUserCredential = userCredential; 460 } 461 /** 462 * Get the user credential information. 463 * 464 * @return user credential information 465 */ getUserCredential()466 public UserCredential getUserCredential() { 467 return mUserCredential; 468 } 469 470 /** 471 * Certificate based credential. This is used for EAP-TLS. 472 * Contains fields under PerProviderSubscription/Credential/DigitalCertificate subtree. 473 */ 474 public static final class CertificateCredential implements Parcelable { 475 /** 476 * Supported certificate types. 477 * @hide 478 */ 479 public static final String CERT_TYPE_X509V3 = "x509v3"; 480 481 /** 482 * Certificate SHA-256 fingerprint length. 483 */ 484 private static final int CERT_SHA256_FINGER_PRINT_LENGTH = 32; 485 486 /** 487 * Certificate type. 488 */ 489 private String mCertType = null; 490 /** 491 * Set the certificate type associated with this certificate credential. 492 * 493 * @param certType The certificate type to set to 494 */ setCertType(String certType)495 public void setCertType(String certType) { 496 mCertType = certType; 497 } 498 /** 499 * Get the certificate type associated with this certificate credential. 500 * 501 * @return certificate type 502 */ getCertType()503 public String getCertType() { 504 return mCertType; 505 } 506 507 /** 508 * The SHA-256 fingerprint of the certificate. 509 */ 510 private byte[] mCertSha256Fingerprint = null; 511 /** 512 * Set the certificate SHA-256 fingerprint associated with this certificate credential. 513 * 514 * @param certSha256Fingerprint The certificate fingerprint to set to 515 */ setCertSha256Fingerprint(byte[] certSha256Fingerprint)516 public void setCertSha256Fingerprint(byte[] certSha256Fingerprint) { 517 mCertSha256Fingerprint = certSha256Fingerprint; 518 } 519 /** 520 * Get the certificate SHA-256 fingerprint associated with this certificate credential. 521 * 522 * @return certificate SHA-256 fingerprint 523 */ getCertSha256Fingerprint()524 public byte[] getCertSha256Fingerprint() { 525 return mCertSha256Fingerprint; 526 } 527 528 /** 529 * Constructor for creating CertificateCredential with default values. 530 */ CertificateCredential()531 public CertificateCredential() {} 532 533 /** 534 * Copy constructor. 535 * 536 * @param source The source to copy from 537 */ CertificateCredential(CertificateCredential source)538 public CertificateCredential(CertificateCredential source) { 539 if (source != null) { 540 mCertType = source.mCertType; 541 if (source.mCertSha256Fingerprint != null) { 542 mCertSha256Fingerprint = Arrays.copyOf(source.mCertSha256Fingerprint, 543 source.mCertSha256Fingerprint.length); 544 } 545 } 546 } 547 548 @Override describeContents()549 public int describeContents() { 550 return 0; 551 } 552 553 @Override writeToParcel(Parcel dest, int flags)554 public void writeToParcel(Parcel dest, int flags) { 555 dest.writeString(mCertType); 556 dest.writeByteArray(mCertSha256Fingerprint); 557 } 558 559 @Override equals(Object thatObject)560 public boolean equals(Object thatObject) { 561 if (this == thatObject) { 562 return true; 563 } 564 if (!(thatObject instanceof CertificateCredential)) { 565 return false; 566 } 567 568 CertificateCredential that = (CertificateCredential) thatObject; 569 return TextUtils.equals(mCertType, that.mCertType) 570 && Arrays.equals(mCertSha256Fingerprint, that.mCertSha256Fingerprint); 571 } 572 573 @Override hashCode()574 public int hashCode() { 575 return Objects.hash(mCertType, Arrays.hashCode(mCertSha256Fingerprint)); 576 } 577 578 @Override toString()579 public String toString() { 580 return "CertificateType: " + mCertType + "\n"; 581 } 582 583 /** 584 * Validate the configuration data. 585 * 586 * @return true on success or false on failure 587 * @hide 588 */ validate()589 public boolean validate() { 590 if (!TextUtils.equals(CERT_TYPE_X509V3, mCertType)) { 591 Log.d(TAG, "Unsupported certificate type: " + mCertType); 592 return false; 593 } 594 if (mCertSha256Fingerprint == null 595 || mCertSha256Fingerprint.length != CERT_SHA256_FINGER_PRINT_LENGTH) { 596 Log.d(TAG, "Invalid SHA-256 fingerprint"); 597 return false; 598 } 599 return true; 600 } 601 602 public static final @android.annotation.NonNull Creator<CertificateCredential> CREATOR = 603 new Creator<CertificateCredential>() { 604 @Override 605 public CertificateCredential createFromParcel(Parcel in) { 606 CertificateCredential certCredential = new CertificateCredential(); 607 certCredential.setCertType(in.readString()); 608 certCredential.setCertSha256Fingerprint(in.createByteArray()); 609 return certCredential; 610 } 611 612 @Override 613 public CertificateCredential[] newArray(int size) { 614 return new CertificateCredential[size]; 615 } 616 }; 617 } 618 private CertificateCredential mCertCredential = null; 619 /** 620 * Set the certificate credential information. 621 * 622 * @param certCredential The certificate credential to set to 623 */ setCertCredential(CertificateCredential certCredential)624 public void setCertCredential(CertificateCredential certCredential) { 625 mCertCredential = certCredential; 626 } 627 /** 628 * Get the certificate credential information. 629 * 630 * @return certificate credential information 631 */ getCertCredential()632 public CertificateCredential getCertCredential() { 633 return mCertCredential; 634 } 635 636 /** 637 * SIM (Subscriber Identify Module) based credential. 638 * Contains fields under PerProviderSubscription/Credential/SIM subtree. 639 */ 640 public static final class SimCredential implements Parcelable { 641 /** 642 * Maximum string length for IMSI. 643 */ 644 private static final int MAX_IMSI_LENGTH = 15; 645 646 /** 647 * International Mobile Subscriber Identity, is used to identify the user 648 * of a cellular network and is a unique identification associated with all 649 * cellular networks 650 */ 651 private String mImsi = null; 652 /** 653 * Set the IMSI (International Mobile Subscriber Identity) associated with this SIM 654 * credential. 655 * 656 * @param imsi The IMSI to set to 657 */ setImsi(String imsi)658 public void setImsi(String imsi) { 659 mImsi = imsi; 660 } 661 /** 662 * Get the IMSI (International Mobile Subscriber Identity) associated with this SIM 663 * credential. 664 * 665 * @return IMSI associated with this SIM credential 666 */ getImsi()667 public String getImsi() { 668 return mImsi; 669 } 670 671 /** 672 * EAP (Extensible Authentication Protocol) method type for using SIM credential. 673 * Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4 674 * for valid values. 675 * Using Integer.MIN_VALUE to indicate unset value. 676 */ 677 private int mEapType = Integer.MIN_VALUE; 678 /** 679 * Set the EAP (Extensible Authentication Protocol) method type associated with this 680 * SIM credential. 681 * 682 * @param eapType The EAP method type to set to 683 */ setEapType(int eapType)684 public void setEapType(int eapType) { 685 mEapType = eapType; 686 } 687 /** 688 * Get the EAP (Extensible Authentication Protocol) method type associated with this 689 * SIM credential. 690 * 691 * @return EAP method type associated with this SIM credential 692 */ getEapType()693 public int getEapType() { 694 return mEapType; 695 } 696 697 /** 698 * Constructor for creating SimCredential with default values. 699 */ SimCredential()700 public SimCredential() {} 701 702 /** 703 * Copy constructor 704 * 705 * @param source The source to copy from 706 */ SimCredential(SimCredential source)707 public SimCredential(SimCredential source) { 708 if (source != null) { 709 mImsi = source.mImsi; 710 mEapType = source.mEapType; 711 } 712 } 713 714 @Override describeContents()715 public int describeContents() { 716 return 0; 717 } 718 719 @Override equals(Object thatObject)720 public boolean equals(Object thatObject) { 721 if (this == thatObject) { 722 return true; 723 } 724 if (!(thatObject instanceof SimCredential)) { 725 return false; 726 } 727 728 SimCredential that = (SimCredential) thatObject; 729 return TextUtils.equals(mImsi, that.mImsi) 730 && mEapType == that.mEapType; 731 } 732 733 @Override hashCode()734 public int hashCode() { 735 return Objects.hash(mImsi, mEapType); 736 } 737 738 @Override toString()739 public String toString() { 740 StringBuilder builder = new StringBuilder(); 741 builder.append("IMSI: ").append(mImsi).append("\n"); 742 builder.append("EAPType: ").append(mEapType).append("\n"); 743 return builder.toString(); 744 } 745 746 @Override writeToParcel(Parcel dest, int flags)747 public void writeToParcel(Parcel dest, int flags) { 748 dest.writeString(mImsi); 749 dest.writeInt(mEapType); 750 } 751 752 /** 753 * Validate the configuration data. 754 * 755 * @return true on success or false on failure 756 * @hide 757 */ validate()758 public boolean validate() { 759 // Note: this only validate the format of IMSI string itself. Additional verification 760 // will be done by WifiService at the time of provisioning to verify against the IMSI 761 // of the SIM card installed in the device. 762 if (!verifyImsi()) { 763 return false; 764 } 765 if (mEapType != EAPConstants.EAP_SIM && mEapType != EAPConstants.EAP_AKA 766 && mEapType != EAPConstants.EAP_AKA_PRIME) { 767 Log.d(TAG, "Invalid EAP Type for SIM credential: " + mEapType); 768 return false; 769 } 770 return true; 771 } 772 773 public static final @android.annotation.NonNull Creator<SimCredential> CREATOR = 774 new Creator<SimCredential>() { 775 @Override 776 public SimCredential createFromParcel(Parcel in) { 777 SimCredential simCredential = new SimCredential(); 778 simCredential.setImsi(in.readString()); 779 simCredential.setEapType(in.readInt()); 780 return simCredential; 781 } 782 783 @Override 784 public SimCredential[] newArray(int size) { 785 return new SimCredential[size]; 786 } 787 }; 788 789 /** 790 * Verify the IMSI (International Mobile Subscriber Identity) string. The string 791 * should contain zero or more numeric digits, and might ends with a "*" for prefix 792 * matching. 793 * 794 * @return true if IMSI is valid, false otherwise. 795 */ verifyImsi()796 private boolean verifyImsi() { 797 if (TextUtils.isEmpty(mImsi)) { 798 Log.d(TAG, "Missing IMSI"); 799 return false; 800 } 801 if (mImsi.length() > MAX_IMSI_LENGTH) { 802 Log.d(TAG, "IMSI exceeding maximum length: " + mImsi.length()); 803 return false; 804 } 805 806 // Locate the first non-digit character. 807 int nonDigit; 808 char stopChar = '\0'; 809 for (nonDigit = 0; nonDigit < mImsi.length(); nonDigit++) { 810 stopChar = mImsi.charAt(nonDigit); 811 if (stopChar < '0' || stopChar > '9') { 812 break; 813 } 814 } 815 816 if (nonDigit == mImsi.length()) { 817 return true; 818 } 819 else if (nonDigit == mImsi.length()-1 && stopChar == '*') { 820 // Prefix matching. 821 return true; 822 } 823 return false; 824 } 825 } 826 private SimCredential mSimCredential = null; 827 /** 828 * Set the SIM credential information. 829 * 830 * @param simCredential The SIM credential to set to 831 */ setSimCredential(SimCredential simCredential)832 public void setSimCredential(SimCredential simCredential) { 833 mSimCredential = simCredential; 834 } 835 /** 836 * Get the SIM credential information. 837 * 838 * @return SIM credential information 839 */ getSimCredential()840 public SimCredential getSimCredential() { 841 return mSimCredential; 842 } 843 844 /** 845 * CA (Certificate Authority) X509 certificates. 846 */ 847 private X509Certificate[] mCaCertificates = null; 848 849 /** 850 * Set the CA (Certification Authority) certificate associated with this credential. 851 * 852 * @param caCertificate The CA certificate to set to 853 */ setCaCertificate(X509Certificate caCertificate)854 public void setCaCertificate(X509Certificate caCertificate) { 855 mCaCertificates = null; 856 if (caCertificate != null) { 857 mCaCertificates = new X509Certificate[] {caCertificate}; 858 } 859 } 860 861 /** 862 * Set the CA (Certification Authority) certificates associated with this credential. 863 * 864 * @param caCertificates The list of CA certificates to set to 865 * @hide 866 */ setCaCertificates(X509Certificate[] caCertificates)867 public void setCaCertificates(X509Certificate[] caCertificates) { 868 mCaCertificates = caCertificates; 869 } 870 871 /** 872 * Get the CA (Certification Authority) certificate associated with this credential. 873 * 874 * @return CA certificate associated with this credential, {@code null} if certificate is not 875 * set or certificate is more than one. 876 */ getCaCertificate()877 public X509Certificate getCaCertificate() { 878 return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0]; 879 } 880 881 /** 882 * Get the CA (Certification Authority) certificates associated with this credential. 883 * 884 * @return The list of CA certificates associated with this credential 885 * @hide 886 */ getCaCertificates()887 public X509Certificate[] getCaCertificates() { 888 return mCaCertificates; 889 } 890 891 /** 892 * Client side X509 certificate chain. 893 */ 894 private X509Certificate[] mClientCertificateChain = null; 895 /** 896 * Set the client certificate chain associated with this credential. 897 * 898 * @param certificateChain The client certificate chain to set to 899 */ setClientCertificateChain(X509Certificate[] certificateChain)900 public void setClientCertificateChain(X509Certificate[] certificateChain) { 901 mClientCertificateChain = certificateChain; 902 } 903 /** 904 * Get the client certificate chain associated with this credential. 905 * 906 * @return client certificate chain associated with this credential 907 */ getClientCertificateChain()908 public X509Certificate[] getClientCertificateChain() { 909 return mClientCertificateChain; 910 } 911 912 /** 913 * Client side private key. 914 */ 915 private PrivateKey mClientPrivateKey = null; 916 /** 917 * Set the client private key associated with this credential. 918 * 919 * @param clientPrivateKey the client private key to set to 920 */ setClientPrivateKey(PrivateKey clientPrivateKey)921 public void setClientPrivateKey(PrivateKey clientPrivateKey) { 922 mClientPrivateKey = clientPrivateKey; 923 } 924 /** 925 * Get the client private key associated with this credential. 926 * 927 * @return client private key associated with this credential. 928 */ getClientPrivateKey()929 public PrivateKey getClientPrivateKey() { 930 return mClientPrivateKey; 931 } 932 933 /** 934 * Constructor for creating Credential with default values. 935 */ Credential()936 public Credential() {} 937 938 /** 939 * Copy constructor. 940 * 941 * @param source The source to copy from 942 */ Credential(Credential source)943 public Credential(Credential source) { 944 if (source != null) { 945 mCreationTimeInMillis = source.mCreationTimeInMillis; 946 mExpirationTimeInMillis = source.mExpirationTimeInMillis; 947 mRealm = source.mRealm; 948 mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus; 949 if (source.mUserCredential != null) { 950 mUserCredential = new UserCredential(source.mUserCredential); 951 } 952 if (source.mCertCredential != null) { 953 mCertCredential = new CertificateCredential(source.mCertCredential); 954 } 955 if (source.mSimCredential != null) { 956 mSimCredential = new SimCredential(source.mSimCredential); 957 } 958 if (source.mClientCertificateChain != null) { 959 mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain, 960 source.mClientCertificateChain.length); 961 } 962 if (source.mCaCertificates != null) { 963 mCaCertificates = Arrays.copyOf(source.mCaCertificates, 964 source.mCaCertificates.length); 965 } 966 967 mClientPrivateKey = source.mClientPrivateKey; 968 } 969 } 970 971 @Override describeContents()972 public int describeContents() { 973 return 0; 974 } 975 976 @Override writeToParcel(Parcel dest, int flags)977 public void writeToParcel(Parcel dest, int flags) { 978 dest.writeLong(mCreationTimeInMillis); 979 dest.writeLong(mExpirationTimeInMillis); 980 dest.writeString(mRealm); 981 dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0); 982 dest.writeParcelable(mUserCredential, flags); 983 dest.writeParcelable(mCertCredential, flags); 984 dest.writeParcelable(mSimCredential, flags); 985 ParcelUtil.writeCertificates(dest, mCaCertificates); 986 ParcelUtil.writeCertificates(dest, mClientCertificateChain); 987 ParcelUtil.writePrivateKey(dest, mClientPrivateKey); 988 } 989 990 @Override equals(Object thatObject)991 public boolean equals(Object thatObject) { 992 if (this == thatObject) { 993 return true; 994 } 995 if (!(thatObject instanceof Credential)) { 996 return false; 997 } 998 999 Credential that = (Credential) thatObject; 1000 return TextUtils.equals(mRealm, that.mRealm) 1001 && mCreationTimeInMillis == that.mCreationTimeInMillis 1002 && mExpirationTimeInMillis == that.mExpirationTimeInMillis 1003 && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus 1004 && (mUserCredential == null ? that.mUserCredential == null 1005 : mUserCredential.equals(that.mUserCredential)) 1006 && (mCertCredential == null ? that.mCertCredential == null 1007 : mCertCredential.equals(that.mCertCredential)) 1008 && (mSimCredential == null ? that.mSimCredential == null 1009 : mSimCredential.equals(that.mSimCredential)) 1010 && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates) 1011 && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain) 1012 && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey); 1013 } 1014 1015 @Override hashCode()1016 public int hashCode() { 1017 return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm, 1018 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential, 1019 mClientPrivateKey, Arrays.hashCode(mCaCertificates), 1020 Arrays.hashCode(mClientCertificateChain)); 1021 } 1022 1023 @Override toString()1024 public String toString() { 1025 StringBuilder builder = new StringBuilder(); 1026 builder.append("Realm: ").append(mRealm).append("\n"); 1027 builder.append("CreationTime: ").append(mCreationTimeInMillis != Long.MIN_VALUE 1028 ? new Date(mCreationTimeInMillis) : "Not specified").append("\n"); 1029 builder.append("ExpirationTime: ").append(mExpirationTimeInMillis != Long.MIN_VALUE 1030 ? new Date(mExpirationTimeInMillis) : "Not specified").append("\n"); 1031 builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n"); 1032 if (mUserCredential != null) { 1033 builder.append("UserCredential Begin ---\n"); 1034 builder.append(mUserCredential); 1035 builder.append("UserCredential End ---\n"); 1036 } 1037 if (mCertCredential != null) { 1038 builder.append("CertificateCredential Begin ---\n"); 1039 builder.append(mCertCredential); 1040 builder.append("CertificateCredential End ---\n"); 1041 } 1042 if (mSimCredential != null) { 1043 builder.append("SIMCredential Begin ---\n"); 1044 builder.append(mSimCredential); 1045 builder.append("SIMCredential End ---\n"); 1046 } 1047 return builder.toString(); 1048 } 1049 1050 /** 1051 * Validate the configuration data. 1052 * 1053 * @param isR1 {@code true} if the configuration is for R1 1054 * @return true on success or false on failure 1055 * @hide 1056 */ validate(boolean isR1)1057 public boolean validate(boolean isR1) { 1058 if (TextUtils.isEmpty(mRealm)) { 1059 Log.d(TAG, "Missing realm"); 1060 return false; 1061 } 1062 if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) { 1063 Log.d(TAG, "realm exceeding maximum length: " 1064 + mRealm.getBytes(StandardCharsets.UTF_8).length); 1065 return false; 1066 } 1067 1068 // Verify the credential. 1069 if (mUserCredential != null) { 1070 if (!verifyUserCredential(isR1)) { 1071 return false; 1072 } 1073 } else if (mCertCredential != null) { 1074 if (!verifyCertCredential(isR1)) { 1075 return false; 1076 } 1077 } else if (mSimCredential != null) { 1078 if (!verifySimCredential()) { 1079 return false; 1080 } 1081 } else { 1082 Log.d(TAG, "Missing required credential"); 1083 return false; 1084 } 1085 1086 return true; 1087 } 1088 1089 public static final @android.annotation.NonNull Creator<Credential> CREATOR = 1090 new Creator<Credential>() { 1091 @Override 1092 public Credential createFromParcel(Parcel in) { 1093 Credential credential = new Credential(); 1094 credential.setCreationTimeInMillis(in.readLong()); 1095 credential.setExpirationTimeInMillis(in.readLong()); 1096 credential.setRealm(in.readString()); 1097 credential.setCheckAaaServerCertStatus(in.readInt() != 0); 1098 credential.setUserCredential(in.readParcelable(null)); 1099 credential.setCertCredential(in.readParcelable(null)); 1100 credential.setSimCredential(in.readParcelable(null)); 1101 credential.setCaCertificates(ParcelUtil.readCertificates(in)); 1102 credential.setClientCertificateChain(ParcelUtil.readCertificates(in)); 1103 credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in)); 1104 return credential; 1105 } 1106 1107 @Override 1108 public Credential[] newArray(int size) { 1109 return new Credential[size]; 1110 } 1111 }; 1112 1113 /** 1114 * Verify user credential. 1115 * 1116 * @param isR1 {@code true} if credential is for R1 1117 * @return true if user credential is valid, false otherwise. 1118 */ verifyUserCredential(boolean isR1)1119 private boolean verifyUserCredential(boolean isR1) { 1120 if (mUserCredential == null) { 1121 Log.d(TAG, "Missing user credential"); 1122 return false; 1123 } 1124 if (mCertCredential != null || mSimCredential != null) { 1125 Log.d(TAG, "Contained more than one type of credential"); 1126 return false; 1127 } 1128 if (!mUserCredential.validate()) { 1129 return false; 1130 } 1131 1132 // CA certificate is required for R1 Passpoint profile. 1133 // For R2, it is downloaded using cert URL provided in PPS MO after validation completes. 1134 if (isR1 && mCaCertificates == null) { 1135 Log.d(TAG, "Missing CA Certificate for user credential"); 1136 return false; 1137 } 1138 return true; 1139 } 1140 1141 /** 1142 * Verify certificate credential, which is used for EAP-TLS. This will verify 1143 * that the necessary client key and certificates are provided. 1144 * 1145 * @param isR1 {@code true} if credential is for R1 1146 * @return true if certificate credential is valid, false otherwise. 1147 */ verifyCertCredential(boolean isR1)1148 private boolean verifyCertCredential(boolean isR1) { 1149 if (mCertCredential == null) { 1150 Log.d(TAG, "Missing certificate credential"); 1151 return false; 1152 } 1153 if (mUserCredential != null || mSimCredential != null) { 1154 Log.d(TAG, "Contained more than one type of credential"); 1155 return false; 1156 } 1157 1158 if (!mCertCredential.validate()) { 1159 return false; 1160 } 1161 1162 // Verify required key and certificates for certificate credential. 1163 // CA certificate is required for R1 Passpoint profile. 1164 // For R2, it is downloaded using cert URL provided in PPS MO after validation completes. 1165 if (isR1 && mCaCertificates == null) { 1166 Log.d(TAG, "Missing CA Certificate for certificate credential"); 1167 return false; 1168 } 1169 if (mClientPrivateKey == null) { 1170 Log.d(TAG, "Missing client private key for certificate credential"); 1171 return false; 1172 } 1173 try { 1174 // Verify SHA-256 fingerprint for client certificate. 1175 if (!verifySha256Fingerprint(mClientCertificateChain, 1176 mCertCredential.getCertSha256Fingerprint())) { 1177 Log.d(TAG, "SHA-256 fingerprint mismatch"); 1178 return false; 1179 } 1180 } catch (NoSuchAlgorithmException | CertificateEncodingException e) { 1181 Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage()); 1182 return false; 1183 } 1184 1185 return true; 1186 } 1187 1188 /** 1189 * Verify SIM credential. 1190 * 1191 * @return true if SIM credential is valid, false otherwise. 1192 */ verifySimCredential()1193 private boolean verifySimCredential() { 1194 if (mSimCredential == null) { 1195 Log.d(TAG, "Missing SIM credential"); 1196 return false; 1197 } 1198 if (mUserCredential != null || mCertCredential != null) { 1199 Log.d(TAG, "Contained more than one type of credential"); 1200 return false; 1201 } 1202 return mSimCredential.validate(); 1203 } 1204 isPrivateKeyEquals(PrivateKey key1, PrivateKey key2)1205 private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) { 1206 if (key1 == null && key2 == null) { 1207 return true; 1208 } 1209 1210 /* Return false if only one of them is null */ 1211 if (key1 == null || key2 == null) { 1212 return false; 1213 } 1214 1215 return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) && 1216 Arrays.equals(key1.getEncoded(), key2.getEncoded()); 1217 } 1218 1219 /** 1220 * Verify two X.509 certificates are identical. 1221 * 1222 * @param cert1 a certificate to compare 1223 * @param cert2 a certificate to compare 1224 * @return {@code true} if given certificates are the same each other, {@code false} otherwise. 1225 * @hide 1226 */ isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2)1227 public static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) { 1228 if (cert1 == null && cert2 == null) { 1229 return true; 1230 } 1231 1232 /* Return false if only one of them is null */ 1233 if (cert1 == null || cert2 == null) { 1234 return false; 1235 } 1236 1237 boolean result = false; 1238 try { 1239 result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded()); 1240 } catch (CertificateEncodingException e) { 1241 /* empty, return false. */ 1242 } 1243 return result; 1244 } 1245 isX509CertificatesEquals(X509Certificate[] certs1, X509Certificate[] certs2)1246 private static boolean isX509CertificatesEquals(X509Certificate[] certs1, 1247 X509Certificate[] certs2) { 1248 if (certs1 == null && certs2 == null) { 1249 return true; 1250 } 1251 1252 /* Return false if only one of them is null */ 1253 if (certs1 == null || certs2 == null) { 1254 return false; 1255 } 1256 1257 if (certs1.length != certs2.length) { 1258 return false; 1259 } 1260 1261 for (int i = 0; i < certs1.length; i++) { 1262 if (!isX509CertificateEquals(certs1[i], certs2[i])) { 1263 return false; 1264 } 1265 } 1266 1267 return true; 1268 } 1269 1270 /** 1271 * Verify that the digest for a certificate in the certificate chain matches expected 1272 * fingerprint. The certificate that matches the fingerprint is the client certificate. 1273 * 1274 * @param certChain Chain of certificates 1275 * @param expectedFingerprint The expected SHA-256 digest of the client certificate 1276 * @return true if the certificate chain contains a matching certificate, false otherwise 1277 * @throws NoSuchAlgorithmException 1278 * @throws CertificateEncodingException 1279 */ verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint)1280 private static boolean verifySha256Fingerprint(X509Certificate[] certChain, 1281 byte[] expectedFingerprint) 1282 throws NoSuchAlgorithmException, CertificateEncodingException { 1283 if (certChain == null) { 1284 return false; 1285 } 1286 MessageDigest digester = MessageDigest.getInstance("SHA-256"); 1287 for (X509Certificate certificate : certChain) { 1288 digester.reset(); 1289 byte[] fingerprint = digester.digest(certificate.getEncoded()); 1290 if (Arrays.equals(expectedFingerprint, fingerprint)) { 1291 return true; 1292 } 1293 } 1294 return false; 1295 } 1296 } 1297