import android.util.Pair;
import java.io.IOException;
+import java.text.DateFormat;
+import java.text.ParseException;
+import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
* Fields under Credential subtree.
*/
private static final String NODE_CREDENTIAL = "Credential";
+ private static final String NODE_CREATION_DATE = "CreationDate";
+ private static final String NODE_EXPIRATION_DATE = "ExpirationDate";
private static final String NODE_USERNAME_PASSWORD = "UsernamePassword";
private static final String NODE_USERNAME = "Username";
private static final String NODE_PASSWORD = "Password";
+ private static final String NODE_MACHINE_MANAGED = "MachineManaged";
+ private static final String NODE_SOFT_TOKEN_APP = "SoftTokenApp";
+ private static final String NODE_ABLE_TO_SHARE = "AbleToShare";
private static final String NODE_EAP_METHOD = "EAPMethod";
private static final String NODE_EAP_TYPE = "EAPType";
+ private static final String NODE_VENDOR_ID = "VendorId";
+ private static final String NODE_VENDOR_TYPE = "VendorType";
+ private static final String NODE_INNER_EAP_TYPE = "InnerEAPType";
+ private static final String NODE_INNER_VENDOR_ID = "InnerVendorID";
+ private static final String NODE_INNER_VENDOR_TYPE = "InnerVendorType";
private static final String NODE_INNER_METHOD = "InnerMethod";
private static final String NODE_DIGITAL_CERTIFICATE = "DigitalCertificate";
private static final String NODE_CERTIFICATE_TYPE = "CertificateType";
private static final String NODE_REALM = "Realm";
private static final String NODE_SIM = "SIM";
private static final String NODE_SIM_IMSI = "IMSI";
+ private static final String NODE_CHECK_AAA_SERVER_CERT_STATUS = "CheckAAAServerCertStatus";
/**
* URN (Unique Resource Name) for PerProviderSubscription Management Object Tree.
Credential credential = new Credential();
for (PPSNode child: node.getChildren()) {
switch (child.getName()) {
+ case NODE_CREATION_DATE:
+ credential.creationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
+ case NODE_EXPIRATION_DATE:
+ credential.expirationTimeInMs = parseDate(getPpsNodeValue(child));
+ break;
case NODE_USERNAME_PASSWORD:
credential.userCredential = parseUserCredential(child);
break;
case NODE_REALM:
credential.realm = getPpsNodeValue(child);
break;
+ case NODE_CHECK_AAA_SERVER_CERT_STATUS:
+ credential.checkAAAServerCertStatus =
+ Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
case NODE_SIM:
credential.simCredential = parseSimCredential(child);
break;
case NODE_PASSWORD:
userCred.password = getPpsNodeValue(child);
break;
+ case NODE_MACHINE_MANAGED:
+ userCred.machineManaged = Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
+ case NODE_SOFT_TOKEN_APP:
+ userCred.softTokenApp = getPpsNodeValue(child);
+ break;
+ case NODE_ABLE_TO_SHARE:
+ userCred.ableToShare = Boolean.parseBoolean(getPpsNodeValue(child));
+ break;
case NODE_EAP_METHOD:
parseEAPMethod(child, userCred);
break;
case NODE_INNER_METHOD:
userCred.nonEapInnerMethod = getPpsNodeValue(child);
break;
+ case NODE_VENDOR_ID:
+ case NODE_VENDOR_TYPE:
+ case NODE_INNER_EAP_TYPE:
+ case NODE_INNER_VENDOR_ID:
+ case NODE_INNER_VENDOR_TYPE:
+ // Only EAP-TTLS is currently supported for user credential, which doesn't
+ // use any of these parameters.
+ Log.d(TAG, "Ignore unsupported EAP method parameter: " + child.getName());
+ break;
default:
throw new ParsingException("Unknown node under EAPMethod: " + child.getName());
}
}
/**
+ * Convert a date string to the number of milliseconds since January 1, 1970, 00:00:00 GMT.
+ *
+ * @param dateStr String in the format of yyyy-MM-dd'T'HH:mm:ss'Z'
+ * @return number of milliseconds
+ * @throws ParsingException
+ */
+ private static long parseDate(String dateStr) throws ParsingException {
+ try {
+ DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'");
+ return format.parse(dateStr).getTime();
+ } catch (ParseException pe) {
+ throw new ParsingException("Badly formatted time: " + dateStr);
+ }
+ }
+
+ /**
* Parse an integer string.
*
* @param value String of integer value
import android.text.TextUtils;
import android.util.Log;
+import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
* In addition to the fields in the Credential subtree, this will also maintain necessary
* information for the private key and certificates associated with this credential.
*
- * Currently we only support the nodes that are used by Hotspot 2.0 Release 1.
- *
* @hide
*/
public final class Credential implements Parcelable {
* Max string length for realm. Refer to Credential/Realm node in Hotspot 2.0 Release 2
* Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_REALM_LENGTH = 253;
+ private static final int MAX_REALM_BYTES = 253;
+
+ /**
+ * The time this credential is created. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long creationTimeInMs = Long.MIN_VALUE;
+
+ /**
+ * The time this credential will expire. It is in the format of number
+ * of milliseconds since January 1, 1970, 00:00:00 GMT.
+ * Using Long.MIN_VALUE to indicate unset value.
+ */
+ public long expirationTimeInMs = Long.MIN_VALUE;
/**
* The realm associated with this credential. It will be used to determine
public String realm = null;
/**
+ * When set to true, the device should check AAA (Authentication, Authorization,
+ * and Accounting) server's certificate during EAP (Extensible Authentication
+ * Protocol) authentication.
+ */
+ public boolean checkAAAServerCertStatus = false;
+
+ /**
* Username-password based credential.
* Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree.
*/
* Maximum string length for username. Refer to Credential/UsernamePassword/Username
* node in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_USERNAME_LENGTH = 63;
+ private static final int MAX_USERNAME_BYTES = 63;
/**
* Maximum string length for password. Refer to Credential/UsernamePassword/Password
* in Hotspot 2.0 Release 2 Technical Specification Section 9.1 for more info.
*/
- private static final int MAX_PASSWORD_LENGTH = 255;
+ private static final int MAX_PASSWORD_BYTES = 255;
/**
* Supported Non-EAP inner methods. Refer to
public String password = null;
/**
+ * Flag indicating if the password is machine managed.
+ */
+ public boolean machineManaged = false;
+
+ /**
+ * The name of the application used to generate the password.
+ */
+ public String softTokenApp = null;
+
+ /**
+ * Flag indicating if this credential is usable on other mobile devices as well.
+ */
+ public boolean ableToShare = false;
+
+ /**
* EAP (Extensible Authentication Protocol) method type.
* Refer to http://www.iana.org/assignments/eap-numbers/eap-numbers.xml#eap-numbers-4
* for valid values.
if (source != null) {
username = source.username;
password = source.password;
+ machineManaged = source.machineManaged;
+ softTokenApp = source.softTokenApp;
+ ableToShare = source.ableToShare;
eapType = source.eapType;
nonEapInnerMethod = source.nonEapInnerMethod;
}
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(username);
dest.writeString(password);
+ dest.writeInt(machineManaged ? 1 : 0);
+ dest.writeString(softTokenApp);
+ dest.writeInt(ableToShare ? 1 : 0);
dest.writeInt(eapType);
dest.writeString(nonEapInnerMethod);
}
}
UserCredential that = (UserCredential) thatObject;
- return TextUtils.equals(username, that.username) &&
- TextUtils.equals(password, that.password) &&
- eapType == that.eapType &&
- TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
+ return TextUtils.equals(username, that.username)
+ && TextUtils.equals(password, that.password)
+ && machineManaged == that.machineManaged
+ && TextUtils.equals(softTokenApp, that.softTokenApp)
+ && ableToShare == that.ableToShare
+ && eapType == that.eapType
+ && TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod);
}
/**
Log.d(TAG, "Missing username");
return false;
}
- if (username.length() > MAX_USERNAME_LENGTH) {
- Log.d(TAG, "username exceeding maximum length: " + username.length());
+ if (username.getBytes(StandardCharsets.UTF_8).length > MAX_USERNAME_BYTES) {
+ Log.d(TAG, "username exceeding maximum length: "
+ + username.getBytes(StandardCharsets.UTF_8).length);
return false;
}
Log.d(TAG, "Missing password");
return false;
}
- if (password.length() > MAX_PASSWORD_LENGTH) {
- Log.d(TAG, "password exceeding maximum length: " + password.length());
+ if (password.getBytes(StandardCharsets.UTF_8).length > MAX_PASSWORD_BYTES) {
+ Log.d(TAG, "password exceeding maximum length: "
+ + password.getBytes(StandardCharsets.UTF_8).length);
return false;
}
UserCredential userCredential = new UserCredential();
userCredential.username = in.readString();
userCredential.password = in.readString();
+ userCredential.machineManaged = in.readInt() != 0;
+ userCredential.softTokenApp = in.readString();
+ userCredential.ableToShare = in.readInt() != 0;
userCredential.eapType = in.readInt();
userCredential.nonEapInnerMethod = in.readString();
return userCredential;
}
CertificateCredential that = (CertificateCredential) thatObject;
- return TextUtils.equals(certType, that.certType) &&
- Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
+ return TextUtils.equals(certType, that.certType)
+ && Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint);
}
/**
Log.d(TAG, "Unsupported certificate type: " + certType);
return false;
}
- if (certSha256FingerPrint == null ||
- certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
+ if (certSha256FingerPrint == null
+ || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) {
Log.d(TAG, "Invalid SHA-256 fingerprint");
return false;
}
}
SimCredential that = (SimCredential) thatObject;
- return TextUtils.equals(imsi, that.imsi) &&
- eapType == that.eapType;
+ return TextUtils.equals(imsi, that.imsi)
+ && eapType == that.eapType;
}
@Override
if (!verifyImsi()) {
return false;
}
- if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA &&
- eapType != EAPConstants.EAP_AKA_PRIME) {
+ if (eapType != EAPConstants.EAP_SIM && eapType != EAPConstants.EAP_AKA
+ && eapType != EAPConstants.EAP_AKA_PRIME) {
Log.d(TAG, "Invalid EAP Type for SIM credential: " + eapType);
return false;
}
*/
public Credential(Credential source) {
if (source != null) {
+ creationTimeInMs = source.creationTimeInMs;
+ expirationTimeInMs = source.expirationTimeInMs;
realm = source.realm;
+ checkAAAServerCertStatus = source.checkAAAServerCertStatus;
if (source.userCredential != null) {
userCredential = new UserCredential(source.userCredential);
}
@Override
public void writeToParcel(Parcel dest, int flags) {
+ dest.writeLong(creationTimeInMs);
+ dest.writeLong(expirationTimeInMs);
dest.writeString(realm);
+ dest.writeInt(checkAAAServerCertStatus ? 1 : 0);
dest.writeParcelable(userCredential, flags);
dest.writeParcelable(certCredential, flags);
dest.writeParcelable(simCredential, flags);
}
Credential that = (Credential) thatObject;
- return TextUtils.equals(realm, that.realm) &&
- (userCredential == null ? that.userCredential == null :
- userCredential.equals(that.userCredential)) &&
- (certCredential == null ? that.certCredential == null :
- certCredential.equals(that.certCredential)) &&
- (simCredential == null ? that.simCredential == null :
- simCredential.equals(that.simCredential)) &&
- isX509CertificateEquals(caCertificate, that.caCertificate) &&
- isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain) &&
- isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
+ return TextUtils.equals(realm, that.realm)
+ && creationTimeInMs == that.creationTimeInMs
+ && expirationTimeInMs == that.expirationTimeInMs
+ && checkAAAServerCertStatus == that.checkAAAServerCertStatus
+ && (userCredential == null ? that.userCredential == null
+ : userCredential.equals(that.userCredential))
+ && (certCredential == null ? that.certCredential == null
+ : certCredential.equals(that.certCredential))
+ && (simCredential == null ? that.simCredential == null
+ : simCredential.equals(that.simCredential))
+ && isX509CertificateEquals(caCertificate, that.caCertificate)
+ && isX509CertificatesEquals(clientCertificateChain, that.clientCertificateChain)
+ && isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey);
}
/**
Log.d(TAG, "Missing realm");
return false;
}
- if (realm.length() > MAX_REALM_LENGTH) {
- Log.d(TAG, "realm exceeding maximum length: " + realm.length());
+ if (realm.getBytes(StandardCharsets.UTF_8).length > MAX_REALM_BYTES) {
+ Log.d(TAG, "realm exceeding maximum length: "
+ + realm.getBytes(StandardCharsets.UTF_8).length);
return false;
}
@Override
public Credential createFromParcel(Parcel in) {
Credential credential = new Credential();
+ credential.creationTimeInMs = in.readLong();
+ credential.expirationTimeInMs = in.readLong();
credential.realm = in.readString();
+ credential.checkAAAServerCertStatus = in.readInt() != 0;
credential.userCredential = in.readParcelable(null);
credential.certCredential = in.readParcelable(null);
credential.simCredential = in.readParcelable(null);
*/
@SmallTest
public class CredentialTest {
+ /**
+ * Helper function for generating Credential for testing.
+ *
+ * @param userCred Instance of UserCredential
+ * @param certCred Instance of CertificateCredential
+ * @param simCred Instance of SimCredential
+ * @param caCert CA certificate
+ * @param clientCertificateChain Chain of client certificates
+ * @param clientPrivateKey Client private key
+ * @return {@link Credential}
+ */
private static Credential createCredential(Credential.UserCredential userCred,
Credential.CertificateCredential certCred,
Credential.SimCredential simCred,
X509Certificate[] clientCertificateChain,
PrivateKey clientPrivateKey) {
Credential cred = new Credential();
+ cred.creationTimeInMs = 123455L;
+ cred.expirationTimeInMs = 2310093L;
cred.realm = "realm";
+ cred.checkAAAServerCertStatus = true;
cred.userCredential = userCred;
cred.certCredential = certCred;
cred.simCredential = simCred;
return cred;
}
+ /**
+ * Helper function for generating certificate credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithCertificateCredential() {
Credential.CertificateCredential certCred = new Credential.CertificateCredential();
certCred.certType = "x509v3";
new X509Certificate[] {FakeKeys.CLIENT_CERT}, FakeKeys.RSA_KEY1);
}
+ /**
+ * Helper function for generating SIM credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithSimCredential() {
Credential.SimCredential simCred = new Credential.SimCredential();
simCred.imsi = "1234*";
return createCredential(null, null, simCred, null, null, null);
}
+ /**
+ * Helper function for generating user credential for testing.
+ *
+ * @return {@link Credential}
+ */
private static Credential createCredentialWithUserCredential() {
Credential.UserCredential userCred = new Credential.UserCredential();
userCred.username = "username";
userCred.password = "password";
+ userCred.machineManaged = true;
+ userCred.ableToShare = true;
+ userCred.softTokenApp = "TestApp";
userCred.eapType = EAPConstants.EAP_TTLS;
userCred.nonEapInnerMethod = "MS-CHAP";
return createCredential(userCred, null, null, FakeKeys.CA_CERT0,