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