OSDN Git Service

hotspot2: add support for complete PerProviderSubscription/Credential subtree
authorPeter Qiu <zqiu@google.com>
Wed, 11 Jan 2017 21:39:35 +0000 (13:39 -0800)
committerPeter Qiu <zqiu@google.com>
Wed, 25 Jan 2017 00:18:31 +0000 (16:18 -0800)
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

wifi/java/android/net/wifi/hotspot2/omadm/PPSMOParser.java
wifi/java/android/net/wifi/hotspot2/pps/Credential.java
wifi/tests/assets/pps/PerProviderSubscription.xml
wifi/tests/src/android/net/wifi/hotspot2/omadm/PPSMOParserTest.java
wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java

index 2731f42..98fd0f3 100644 (file)
@@ -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());
             }
@@ -981,6 +1023,22 @@ public final class PPSMOParser {
     }
 
     /**
+     * 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
index 790dfaf..3374f42 100644 (file)
@@ -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
@@ -62,6 +75,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
@@ -97,6 +117,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
          * for valid values.
@@ -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);
index 07ef7b3..3969f69 100644 (file)
       <Node>
         <NodeName>Credential</NodeName>
         <Node>
+          <NodeName>CreationDate</NodeName>
+          <Value>2016-01-01T10:00:00Z</Value>
+        </Node>
+        <Node>
+          <NodeName>ExpirationDate</NodeName>
+          <Value>2016-02-01T10:00:00Z</Value>
+        </Node>
+        <Node>
           <NodeName>Realm</NodeName>
           <Value>shaken.stirred.com</Value>
         </Node>
         <Node>
+          <NodeName>CheckAAAServerCertStatus</NodeName>
+          <Value>true</Value>
+        </Node>
+        <Node>
           <NodeName>UsernamePassword</NodeName>
           <Node>
             <NodeName>Username</NodeName>
             <Value>Ym9uZDAwNw==</Value>
           </Node>
           <Node>
+            <NodeName>MachineManaged</NodeName>
+            <Value>true</Value>
+          </Node>
+          <Node>
+            <NodeName>SoftTokenApp</NodeName>
+            <Value>TestApp</Value>
+          </Node>
+          <Node>
+            <NodeName>AbleToShare</NodeName>
+            <Value>true</Value>
+          </Node>
+          <Node>
             <NodeName>EAPMethod</NodeName>
             <Node>
               <NodeName>EAPType</NodeName>
index e553705..1c7508e 100644 (file)
@@ -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();
index 9c8b749..f571c7f 100644 (file)
@@ -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,