From 444dc5d099788e5aa0fd0e664fd952fbb07a69d4 Mon Sep 17 00:00:00 2001 From: Peter Qiu Date: Wed, 11 Jan 2017 13:39:35 -0800 Subject: [PATCH] hotspot2: add support for complete PerProviderSubscription/Credential subtree Added missing fields in Credential subtree for supporting Release 2 and added parsing support for the new fields. Bug: 34198926 Test: frameworks/base/wifi/test/runtests.sh Change-Id: Ic3665c963ab77ddc4b9a03262517a3c7a4ec3ffc --- .../net/wifi/hotspot2/omadm/PPSMOParser.java | 58 ++++++++ .../net/wifi/hotspot2/pps/Credential.java | 128 +++++++++++++----- .../assets/pps/PerProviderSubscription.xml | 24 ++++ .../wifi/hotspot2/omadm/PPSMOParserTest.java | 11 +- .../net/wifi/hotspot2/pps/CredentialTest.java | 32 +++++ 5 files changed, 219 insertions(+), 34 deletions(-) diff --git a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java index 2731f428bf39f..98fd0f3bde15f 100644 --- a/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java +++ b/wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java @@ -24,6 +24,9 @@ import android.util.Log; 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; @@ -147,11 +150,21 @@ public final class PPSMOParser { * 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"; @@ -159,6 +172,7 @@ public final class PPSMOParser { 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. @@ -812,6 +826,12 @@ public final class PPSMOParser { 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; @@ -821,6 +841,10 @@ public final class PPSMOParser { 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; @@ -855,6 +879,15 @@ public final class PPSMOParser { 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; @@ -889,6 +922,15 @@ public final class PPSMOParser { 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()); } @@ -980,6 +1022,22 @@ public final class PPSMOParser { return result; } + /** + * 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. * diff --git a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java index 790dfaf643ca9..3374f42dc1181 100644 --- a/wifi/java/android/net/wifi/hotspot2/pps/Credential.java +++ b/wifi/java/android/net/wifi/hotspot2/pps/Credential.java @@ -23,6 +23,7 @@ import android.os.Parcel; 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; @@ -41,8 +42,6 @@ import java.util.Set; * 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 { @@ -52,7 +51,21 @@ 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 @@ -61,6 +74,13 @@ public final class Credential implements Parcelable { */ 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. @@ -70,13 +90,13 @@ public final class Credential implements Parcelable { * 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 @@ -96,6 +116,21 @@ public final class Credential implements Parcelable { */ 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 @@ -123,6 +158,9 @@ public final class Credential implements Parcelable { if (source != null) { username = source.username; password = source.password; + machineManaged = source.machineManaged; + softTokenApp = source.softTokenApp; + ableToShare = source.ableToShare; eapType = source.eapType; nonEapInnerMethod = source.nonEapInnerMethod; } @@ -137,6 +175,9 @@ public final class Credential implements Parcelable { 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); } @@ -151,10 +192,13 @@ public final class Credential implements Parcelable { } 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); } /** @@ -167,8 +211,9 @@ public final class Credential implements Parcelable { 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; } @@ -176,8 +221,9 @@ public final class Credential implements Parcelable { 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; } @@ -202,6 +248,9 @@ public final class Credential implements Parcelable { 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; @@ -281,8 +330,8 @@ public final class Credential implements Parcelable { } 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); } /** @@ -295,8 +344,8 @@ public final class Credential implements Parcelable { 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; } @@ -378,8 +427,8 @@ public final class Credential implements Parcelable { } SimCredential that = (SimCredential) thatObject; - return TextUtils.equals(imsi, that.imsi) && - eapType == that.eapType; + return TextUtils.equals(imsi, that.imsi) + && eapType == that.eapType; } @Override @@ -400,8 +449,8 @@ public final class Credential implements Parcelable { 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; } @@ -490,7 +539,10 @@ public final class Credential implements Parcelable { */ 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); } @@ -516,7 +568,10 @@ public final class Credential implements Parcelable { @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); @@ -535,16 +590,19 @@ public final class Credential implements Parcelable { } 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); } /** @@ -557,8 +615,9 @@ public final class Credential implements Parcelable { 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; } @@ -588,7 +647,10 @@ public final class Credential implements Parcelable { @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); diff --git a/wifi/tests/assets/pps/PerProviderSubscription.xml b/wifi/tests/assets/pps/PerProviderSubscription.xml index 07ef7b354310d..3969f697a3257 100644 --- a/wifi/tests/assets/pps/PerProviderSubscription.xml +++ b/wifi/tests/assets/pps/PerProviderSubscription.xml @@ -86,10 +86,22 @@ Credential + + CreationDate + 2016-01-01T10:00:00Z + + + ExpirationDate + 2016-02-01T10:00:00Z + Realm shaken.stirred.com + + CheckAAAServerCertStatus + true + UsernamePassword @@ -100,6 +112,18 @@ Password Ym9uZDAwNw== + + MachineManaged + true + + + SoftTokenApp + TestApp + + + AbleToShare + true + EAPMethod diff --git a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java index e5537052dc766..1c7508e7d0f51 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java @@ -31,6 +31,8 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.text.DateFormat; +import java.text.SimpleDateFormat; import java.util.Arrays; import java.util.HashMap; @@ -78,7 +80,7 @@ public class PPSMOParserTest { * * @return {@link PasspointConfiguration} */ - private PasspointConfiguration generateConfigurationFromPPSMOTree() { + private PasspointConfiguration generateConfigurationFromPPSMOTree() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); // HomeSP configuration. @@ -95,11 +97,18 @@ public class PPSMOParserTest { config.homeSp.otherHomePartners = new String[] {"other.fqdn.com"}; // Credential configuration. + DateFormat format = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); config.credential = new Credential(); + config.credential.creationTimeInMs = format.parse("2016-01-01T10:00:00Z").getTime(); + config.credential.expirationTimeInMs = format.parse("2016-02-01T10:00:00Z").getTime(); config.credential.realm = "shaken.stirred.com"; + config.credential.checkAAAServerCertStatus = true; config.credential.userCredential = new Credential.UserCredential(); config.credential.userCredential.username = "james"; config.credential.userCredential.password = "Ym9uZDAwNw=="; + config.credential.userCredential.machineManaged = true; + config.credential.userCredential.softTokenApp = "TestApp"; + config.credential.userCredential.ableToShare = true; config.credential.userCredential.eapType = 21; config.credential.userCredential.nonEapInnerMethod = "MS-CHAP-V2"; config.credential.certCredential = new Credential.CertificateCredential(); diff --git a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java index 9c8b749e1c93a..f571c7fc5966f 100644 --- a/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java +++ b/wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java @@ -37,6 +37,17 @@ import org.junit.Test; */ @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, @@ -44,7 +55,10 @@ public class CredentialTest { 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; @@ -54,6 +68,11 @@ public class CredentialTest { 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"; @@ -62,6 +81,11 @@ public class CredentialTest { 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*"; @@ -69,10 +93,18 @@ public class CredentialTest { 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,