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 String imsi; 742 if (mImsi != null) { 743 if (mImsi.length() > 6 && mImsi.charAt(6) != '*') { 744 // Truncate the full IMSI from the log 745 imsi = mImsi.substring(0, 6) + "****"; 746 } else { 747 imsi = mImsi; 748 } 749 builder.append("IMSI: ").append(imsi).append("\n"); 750 } 751 builder.append("EAPType: ").append(mEapType).append("\n"); 752 return builder.toString(); 753 } 754 755 @Override writeToParcel(Parcel dest, int flags)756 public void writeToParcel(Parcel dest, int flags) { 757 dest.writeString(mImsi); 758 dest.writeInt(mEapType); 759 } 760 761 /** 762 * Validate the configuration data. 763 * 764 * @return true on success or false on failure 765 * @hide 766 */ validate()767 public boolean validate() { 768 // Note: this only validate the format of IMSI string itself. Additional verification 769 // will be done by WifiService at the time of provisioning to verify against the IMSI 770 // of the SIM card installed in the device. 771 if (!verifyImsi()) { 772 return false; 773 } 774 if (mEapType != EAPConstants.EAP_SIM && mEapType != EAPConstants.EAP_AKA 775 && mEapType != EAPConstants.EAP_AKA_PRIME) { 776 Log.d(TAG, "Invalid EAP Type for SIM credential: " + mEapType); 777 return false; 778 } 779 return true; 780 } 781 782 public static final @android.annotation.NonNull Creator<SimCredential> CREATOR = 783 new Creator<SimCredential>() { 784 @Override 785 public SimCredential createFromParcel(Parcel in) { 786 SimCredential simCredential = new SimCredential(); 787 simCredential.setImsi(in.readString()); 788 simCredential.setEapType(in.readInt()); 789 return simCredential; 790 } 791 792 @Override 793 public SimCredential[] newArray(int size) { 794 return new SimCredential[size]; 795 } 796 }; 797 798 /** 799 * Verify the IMSI (International Mobile Subscriber Identity) string. The string 800 * should contain zero or more numeric digits, and might ends with a "*" for prefix 801 * matching. 802 * 803 * @return true if IMSI is valid, false otherwise. 804 */ verifyImsi()805 private boolean verifyImsi() { 806 if (TextUtils.isEmpty(mImsi)) { 807 Log.d(TAG, "Missing IMSI"); 808 return false; 809 } 810 if (mImsi.length() > MAX_IMSI_LENGTH) { 811 Log.d(TAG, "IMSI exceeding maximum length: " + mImsi.length()); 812 return false; 813 } 814 815 // Locate the first non-digit character. 816 int nonDigit; 817 char stopChar = '\0'; 818 for (nonDigit = 0; nonDigit < mImsi.length(); nonDigit++) { 819 stopChar = mImsi.charAt(nonDigit); 820 if (stopChar < '0' || stopChar > '9') { 821 break; 822 } 823 } 824 825 if (nonDigit == mImsi.length()) { 826 return true; 827 } 828 else if (nonDigit == mImsi.length()-1 && stopChar == '*') { 829 // Prefix matching. 830 return true; 831 } 832 return false; 833 } 834 } 835 private SimCredential mSimCredential = null; 836 /** 837 * Set the SIM credential information. 838 * 839 * @param simCredential The SIM credential to set to 840 */ setSimCredential(SimCredential simCredential)841 public void setSimCredential(SimCredential simCredential) { 842 mSimCredential = simCredential; 843 } 844 /** 845 * Get the SIM credential information. 846 * 847 * @return SIM credential information 848 */ getSimCredential()849 public SimCredential getSimCredential() { 850 return mSimCredential; 851 } 852 853 /** 854 * CA (Certificate Authority) X509 certificates. 855 */ 856 private X509Certificate[] mCaCertificates = null; 857 858 /** 859 * Set the CA (Certification Authority) certificate associated with this credential. 860 * 861 * @param caCertificate The CA certificate to set to 862 */ setCaCertificate(X509Certificate caCertificate)863 public void setCaCertificate(X509Certificate caCertificate) { 864 mCaCertificates = null; 865 if (caCertificate != null) { 866 mCaCertificates = new X509Certificate[] {caCertificate}; 867 } 868 } 869 870 /** 871 * Set the CA (Certification Authority) certificates associated with this credential. 872 * 873 * @param caCertificates The list of CA certificates to set to 874 * @hide 875 */ setCaCertificates(X509Certificate[] caCertificates)876 public void setCaCertificates(X509Certificate[] caCertificates) { 877 mCaCertificates = caCertificates; 878 } 879 880 /** 881 * Get the CA (Certification Authority) certificate associated with this credential. 882 * 883 * @return CA certificate associated with this credential, {@code null} if certificate is not 884 * set or certificate is more than one. 885 */ getCaCertificate()886 public X509Certificate getCaCertificate() { 887 return mCaCertificates == null || mCaCertificates.length > 1 ? null : mCaCertificates[0]; 888 } 889 890 /** 891 * Get the CA (Certification Authority) certificates associated with this credential. 892 * 893 * @return The list of CA certificates associated with this credential 894 * @hide 895 */ getCaCertificates()896 public X509Certificate[] getCaCertificates() { 897 return mCaCertificates; 898 } 899 900 /** 901 * Client side X509 certificate chain. 902 */ 903 private X509Certificate[] mClientCertificateChain = null; 904 /** 905 * Set the client certificate chain associated with this credential. 906 * 907 * @param certificateChain The client certificate chain to set to 908 */ setClientCertificateChain(X509Certificate[] certificateChain)909 public void setClientCertificateChain(X509Certificate[] certificateChain) { 910 mClientCertificateChain = certificateChain; 911 } 912 /** 913 * Get the client certificate chain associated with this credential. 914 * 915 * @return client certificate chain associated with this credential 916 */ getClientCertificateChain()917 public X509Certificate[] getClientCertificateChain() { 918 return mClientCertificateChain; 919 } 920 921 /** 922 * Client side private key. 923 */ 924 private PrivateKey mClientPrivateKey = null; 925 /** 926 * Set the client private key associated with this credential. 927 * 928 * @param clientPrivateKey the client private key to set to 929 */ setClientPrivateKey(PrivateKey clientPrivateKey)930 public void setClientPrivateKey(PrivateKey clientPrivateKey) { 931 mClientPrivateKey = clientPrivateKey; 932 } 933 /** 934 * Get the client private key associated with this credential. 935 * 936 * @return client private key associated with this credential. 937 */ getClientPrivateKey()938 public PrivateKey getClientPrivateKey() { 939 return mClientPrivateKey; 940 } 941 942 /** 943 * Constructor for creating Credential with default values. 944 */ Credential()945 public Credential() {} 946 947 /** 948 * Copy constructor. 949 * 950 * @param source The source to copy from 951 */ Credential(Credential source)952 public Credential(Credential source) { 953 if (source != null) { 954 mCreationTimeInMillis = source.mCreationTimeInMillis; 955 mExpirationTimeInMillis = source.mExpirationTimeInMillis; 956 mRealm = source.mRealm; 957 mCheckAaaServerCertStatus = source.mCheckAaaServerCertStatus; 958 if (source.mUserCredential != null) { 959 mUserCredential = new UserCredential(source.mUserCredential); 960 } 961 if (source.mCertCredential != null) { 962 mCertCredential = new CertificateCredential(source.mCertCredential); 963 } 964 if (source.mSimCredential != null) { 965 mSimCredential = new SimCredential(source.mSimCredential); 966 } 967 if (source.mClientCertificateChain != null) { 968 mClientCertificateChain = Arrays.copyOf(source.mClientCertificateChain, 969 source.mClientCertificateChain.length); 970 } 971 if (source.mCaCertificates != null) { 972 mCaCertificates = Arrays.copyOf(source.mCaCertificates, 973 source.mCaCertificates.length); 974 } 975 976 mClientPrivateKey = source.mClientPrivateKey; 977 } 978 } 979 980 @Override describeContents()981 public int describeContents() { 982 return 0; 983 } 984 985 @Override writeToParcel(Parcel dest, int flags)986 public void writeToParcel(Parcel dest, int flags) { 987 dest.writeLong(mCreationTimeInMillis); 988 dest.writeLong(mExpirationTimeInMillis); 989 dest.writeString(mRealm); 990 dest.writeInt(mCheckAaaServerCertStatus ? 1 : 0); 991 dest.writeParcelable(mUserCredential, flags); 992 dest.writeParcelable(mCertCredential, flags); 993 dest.writeParcelable(mSimCredential, flags); 994 ParcelUtil.writeCertificates(dest, mCaCertificates); 995 ParcelUtil.writeCertificates(dest, mClientCertificateChain); 996 ParcelUtil.writePrivateKey(dest, mClientPrivateKey); 997 } 998 999 @Override equals(Object thatObject)1000 public boolean equals(Object thatObject) { 1001 if (this == thatObject) { 1002 return true; 1003 } 1004 if (!(thatObject instanceof Credential)) { 1005 return false; 1006 } 1007 1008 Credential that = (Credential) thatObject; 1009 return TextUtils.equals(mRealm, that.mRealm) 1010 && mCreationTimeInMillis == that.mCreationTimeInMillis 1011 && mExpirationTimeInMillis == that.mExpirationTimeInMillis 1012 && mCheckAaaServerCertStatus == that.mCheckAaaServerCertStatus 1013 && (mUserCredential == null ? that.mUserCredential == null 1014 : mUserCredential.equals(that.mUserCredential)) 1015 && (mCertCredential == null ? that.mCertCredential == null 1016 : mCertCredential.equals(that.mCertCredential)) 1017 && (mSimCredential == null ? that.mSimCredential == null 1018 : mSimCredential.equals(that.mSimCredential)) 1019 && isX509CertificatesEquals(mCaCertificates, that.mCaCertificates) 1020 && isX509CertificatesEquals(mClientCertificateChain, that.mClientCertificateChain) 1021 && isPrivateKeyEquals(mClientPrivateKey, that.mClientPrivateKey); 1022 } 1023 1024 @Override hashCode()1025 public int hashCode() { 1026 return Objects.hash(mCreationTimeInMillis, mExpirationTimeInMillis, mRealm, 1027 mCheckAaaServerCertStatus, mUserCredential, mCertCredential, mSimCredential, 1028 mClientPrivateKey, Arrays.hashCode(mCaCertificates), 1029 Arrays.hashCode(mClientCertificateChain)); 1030 } 1031 1032 /** 1033 * Get a unique identifier for Credential. This identifier depends only on items that remain 1034 * constant throughout the lifetime of a subscription's credentials. 1035 * 1036 * @hide 1037 * @return a Unique identifier for a Credential object 1038 */ getUniqueId()1039 public int getUniqueId() { 1040 return Objects.hash(mUserCredential, mCertCredential, mSimCredential, mRealm); 1041 } 1042 1043 @Override toString()1044 public String toString() { 1045 StringBuilder builder = new StringBuilder(); 1046 builder.append("Realm: ").append(mRealm).append("\n"); 1047 builder.append("CreationTime: ").append(mCreationTimeInMillis != Long.MIN_VALUE 1048 ? new Date(mCreationTimeInMillis) : "Not specified").append("\n"); 1049 builder.append("ExpirationTime: ").append(mExpirationTimeInMillis != Long.MIN_VALUE 1050 ? new Date(mExpirationTimeInMillis) : "Not specified").append("\n"); 1051 builder.append("CheckAAAServerStatus: ").append(mCheckAaaServerCertStatus).append("\n"); 1052 if (mUserCredential != null) { 1053 builder.append("UserCredential Begin ---\n"); 1054 builder.append(mUserCredential); 1055 builder.append("UserCredential End ---\n"); 1056 } 1057 if (mCertCredential != null) { 1058 builder.append("CertificateCredential Begin ---\n"); 1059 builder.append(mCertCredential); 1060 builder.append("CertificateCredential End ---\n"); 1061 } 1062 if (mSimCredential != null) { 1063 builder.append("SIMCredential Begin ---\n"); 1064 builder.append(mSimCredential); 1065 builder.append("SIMCredential End ---\n"); 1066 } 1067 return builder.toString(); 1068 } 1069 1070 /** 1071 * Validate the configuration data. 1072 * 1073 * @return true on success or false on failure 1074 * @hide 1075 */ validate()1076 public boolean validate() { 1077 if (TextUtils.isEmpty(mRealm)) { 1078 Log.d(TAG, "Missing realm"); 1079 return false; 1080 } 1081 if (mRealm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) { 1082 Log.d(TAG, "realm exceeding maximum length: " 1083 + mRealm.getBytes(StandardCharsets.UTF_8).length); 1084 return false; 1085 } 1086 1087 // Verify the credential. 1088 if (mUserCredential != null) { 1089 if (!verifyUserCredential()) { 1090 return false; 1091 } 1092 } else if (mCertCredential != null) { 1093 if (!verifyCertCredential()) { 1094 return false; 1095 } 1096 } else if (mSimCredential != null) { 1097 if (!verifySimCredential()) { 1098 return false; 1099 } 1100 } else { 1101 Log.d(TAG, "Missing required credential"); 1102 return false; 1103 } 1104 1105 return true; 1106 } 1107 1108 public static final @android.annotation.NonNull Creator<Credential> CREATOR = 1109 new Creator<Credential>() { 1110 @Override 1111 public Credential createFromParcel(Parcel in) { 1112 Credential credential = new Credential(); 1113 credential.setCreationTimeInMillis(in.readLong()); 1114 credential.setExpirationTimeInMillis(in.readLong()); 1115 credential.setRealm(in.readString()); 1116 credential.setCheckAaaServerCertStatus(in.readInt() != 0); 1117 credential.setUserCredential(in.readParcelable(null)); 1118 credential.setCertCredential(in.readParcelable(null)); 1119 credential.setSimCredential(in.readParcelable(null)); 1120 credential.setCaCertificates(ParcelUtil.readCertificates(in)); 1121 credential.setClientCertificateChain(ParcelUtil.readCertificates(in)); 1122 credential.setClientPrivateKey(ParcelUtil.readPrivateKey(in)); 1123 return credential; 1124 } 1125 1126 @Override 1127 public Credential[] newArray(int size) { 1128 return new Credential[size]; 1129 } 1130 }; 1131 1132 /** 1133 * Verify user credential. 1134 * If no CA certificate is provided, then the system uses the CAs in the trust store. 1135 * 1136 * @return true if user credential is valid, false otherwise. 1137 */ verifyUserCredential()1138 private boolean verifyUserCredential() { 1139 if (mUserCredential == null) { 1140 Log.d(TAG, "Missing user credential"); 1141 return false; 1142 } 1143 if (mCertCredential != null || mSimCredential != null) { 1144 Log.d(TAG, "Contained more than one type of credential"); 1145 return false; 1146 } 1147 if (!mUserCredential.validate()) { 1148 return false; 1149 } 1150 1151 return true; 1152 } 1153 1154 /** 1155 * Verify certificate credential, which is used for EAP-TLS. This will verify 1156 * that the necessary client key and certificates are provided. 1157 * If no CA certificate is provided, then the system uses the CAs in the trust store. 1158 * 1159 * @return true if certificate credential is valid, false otherwise. 1160 */ verifyCertCredential()1161 private boolean verifyCertCredential() { 1162 if (mCertCredential == null) { 1163 Log.d(TAG, "Missing certificate credential"); 1164 return false; 1165 } 1166 if (mUserCredential != null || mSimCredential != null) { 1167 Log.d(TAG, "Contained more than one type of credential"); 1168 return false; 1169 } 1170 1171 if (!mCertCredential.validate()) { 1172 return false; 1173 } 1174 1175 if (mClientPrivateKey == null) { 1176 Log.d(TAG, "Missing client private key for certificate credential"); 1177 return false; 1178 } 1179 try { 1180 // Verify SHA-256 fingerprint for client certificate. 1181 if (!verifySha256Fingerprint(mClientCertificateChain, 1182 mCertCredential.getCertSha256Fingerprint())) { 1183 Log.d(TAG, "SHA-256 fingerprint mismatch"); 1184 return false; 1185 } 1186 } catch (NoSuchAlgorithmException | CertificateEncodingException e) { 1187 Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage()); 1188 return false; 1189 } 1190 1191 return true; 1192 } 1193 1194 /** 1195 * Verify SIM credential. 1196 * 1197 * @return true if SIM credential is valid, false otherwise. 1198 */ verifySimCredential()1199 private boolean verifySimCredential() { 1200 if (mSimCredential == null) { 1201 Log.d(TAG, "Missing SIM credential"); 1202 return false; 1203 } 1204 if (mUserCredential != null || mCertCredential != null) { 1205 Log.d(TAG, "Contained more than one type of credential"); 1206 return false; 1207 } 1208 return mSimCredential.validate(); 1209 } 1210 isPrivateKeyEquals(PrivateKey key1, PrivateKey key2)1211 private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) { 1212 if (key1 == null && key2 == null) { 1213 return true; 1214 } 1215 1216 /* Return false if only one of them is null */ 1217 if (key1 == null || key2 == null) { 1218 return false; 1219 } 1220 1221 return TextUtils.equals(key1.getAlgorithm(), key2.getAlgorithm()) && 1222 Arrays.equals(key1.getEncoded(), key2.getEncoded()); 1223 } 1224 1225 /** 1226 * Verify two X.509 certificates are identical. 1227 * 1228 * @param cert1 a certificate to compare 1229 * @param cert2 a certificate to compare 1230 * @return {@code true} if given certificates are the same each other, {@code false} otherwise. 1231 * @hide 1232 */ isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2)1233 public static boolean isX509CertificateEquals(X509Certificate cert1, X509Certificate cert2) { 1234 if (cert1 == null && cert2 == null) { 1235 return true; 1236 } 1237 1238 /* Return false if only one of them is null */ 1239 if (cert1 == null || cert2 == null) { 1240 return false; 1241 } 1242 1243 boolean result = false; 1244 try { 1245 result = Arrays.equals(cert1.getEncoded(), cert2.getEncoded()); 1246 } catch (CertificateEncodingException e) { 1247 /* empty, return false. */ 1248 } 1249 return result; 1250 } 1251 isX509CertificatesEquals(X509Certificate[] certs1, X509Certificate[] certs2)1252 private static boolean isX509CertificatesEquals(X509Certificate[] certs1, 1253 X509Certificate[] certs2) { 1254 if (certs1 == null && certs2 == null) { 1255 return true; 1256 } 1257 1258 /* Return false if only one of them is null */ 1259 if (certs1 == null || certs2 == null) { 1260 return false; 1261 } 1262 1263 if (certs1.length != certs2.length) { 1264 return false; 1265 } 1266 1267 for (int i = 0; i < certs1.length; i++) { 1268 if (!isX509CertificateEquals(certs1[i], certs2[i])) { 1269 return false; 1270 } 1271 } 1272 1273 return true; 1274 } 1275 1276 /** 1277 * Verify that the digest for a certificate in the certificate chain matches expected 1278 * fingerprint. The certificate that matches the fingerprint is the client certificate. 1279 * 1280 * @param certChain Chain of certificates 1281 * @param expectedFingerprint The expected SHA-256 digest of the client certificate 1282 * @return true if the certificate chain contains a matching certificate, false otherwise 1283 * @throws NoSuchAlgorithmException 1284 * @throws CertificateEncodingException 1285 */ verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint)1286 private static boolean verifySha256Fingerprint(X509Certificate[] certChain, 1287 byte[] expectedFingerprint) 1288 throws NoSuchAlgorithmException, CertificateEncodingException { 1289 if (certChain == null) { 1290 return false; 1291 } 1292 MessageDigest digester = MessageDigest.getInstance("SHA-256"); 1293 for (X509Certificate certificate : certChain) { 1294 digester.reset(); 1295 byte[] fingerprint = digester.digest(certificate.getEncoded()); 1296 if (Arrays.equals(expectedFingerprint, fingerprint)) { 1297 return true; 1298 } 1299 } 1300 return false; 1301 } 1302 } 1303