Loading wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +15 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,21 @@ public final class PasspointConfiguration implements Parcelable { credential.equals(that.credential)); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (homeSp == null || !homeSp.validate()) { return false; } if (credential == null || !credential.validate()) { return false; } return true; } public static final Creator<PasspointConfiguration> CREATOR = new Creator<PasspointConfiguration>() { @Override Loading wifi/java/android/net/wifi/hotspot2/pps/Credential.java +315 −3 Original line number Diff line number Diff line Loading @@ -16,15 +16,21 @@ package android.net.wifi.hotspot2.pps; import android.net.wifi.EAPConstants; import android.net.wifi.ParcelUtil; import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; import android.util.Log; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Class representing Credential subtree in the PerProviderSubscription (PPS) Loading @@ -40,6 +46,14 @@ import java.util.Arrays; * @hide */ public final class Credential implements Parcelable { private static final String TAG = "Credential"; /** * 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; /** * The realm associated with this credential. It will be used to determine * if this credential can be used to authenticate with a given hotspot by Loading @@ -52,6 +66,26 @@ public final class Credential implements Parcelable { * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree. */ public static final class UserCredential 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; /** * 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; /** * Supported Non-EAP inner methods. Refer to * Credential/UsernamePassword/EAPMethod/InnerEAPType in Hotspot 2.0 Release 2 Technical * Specification Section 9.1 for more info. */ private static final Set<String> SUPPORTED_AUTH = new HashSet<String>(Arrays.asList("PAP", "CHAP", "MS-CHAP", "MS-CHAP-V2")); /** * Username of the credential. */ Loading Loading @@ -104,6 +138,44 @@ public final class Credential implements Parcelable { TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (TextUtils.isEmpty(username)) { Log.d(TAG, "Missing username"); return false; } if (username.length() > MAX_USERNAME_LENGTH) { Log.d(TAG, "username exceeding maximum length: " + username.length()); return false; } if (TextUtils.isEmpty(password)) { Log.d(TAG, "Missing password"); return false; } if (password.length() > MAX_PASSWORD_LENGTH) { Log.d(TAG, "password exceeding maximum length: " + password.length()); return false; } // Only supports EAP-TTLS for user credential. if (eapType != EAPConstants.EAP_TTLS) { Log.d(TAG, "Invalid EAP Type for user credential: " + eapType); return false; } // Verify Non-EAP inner method for EAP-TTLS. if (!SUPPORTED_AUTH.contains(nonEapInnerMethod)) { Log.d(TAG, "Invalid non-EAP inner method for EAP-TTLS: " + nonEapInnerMethod); return false; } return true; } public static final Creator<UserCredential> CREATOR = new Creator<UserCredential>() { @Override Loading @@ -125,12 +197,22 @@ public final class Credential implements Parcelable { public UserCredential userCredential = null; /** * Certificate based credential. * Certificate based credential. This is used for EAP-TLS. * Contains fields under PerProviderSubscription/Credential/DigitalCertificate subtree. */ public static final class CertificateCredential implements Parcelable { /** * Certificate type. Valid values are "802.1ar" and "x509v3". * Supported certificate types. */ private static final String CERT_TYPE_X509V3 = "x509v3"; /** * Certificate SHA-256 fingerprint length. */ private static final int CERT_SHA256_FINGER_PRINT_LENGTH = 32; /** * Certificate type. */ public String certType = null; Loading Loading @@ -164,6 +246,24 @@ public final class Credential implements Parcelable { Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (!TextUtils.equals(CERT_TYPE_X509V3, certType)) { Log.d(TAG, "Unsupported certificate type: " + certType); return false; } if (certSha256FingerPrint == null || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) { Log.d(TAG, "Invalid SHA-256 fingerprint"); return false; } return true; } public static final Creator<CertificateCredential> CREATOR = new Creator<CertificateCredential>() { @Override Loading @@ -188,7 +288,14 @@ public final class Credential implements Parcelable { */ public static final class SimCredential implements Parcelable { /** * International Mobile device Subscriber Identity. * Maximum string length for IMSI. */ public static final int MAX_IMSI_LENGTH = 15; /** * International Mobile Subscriber Identity, is used to identify the user * of a cellular network and is a unique identification associated with all * cellular networks */ public String imsi = null; Loading Loading @@ -225,6 +332,26 @@ public final class Credential implements Parcelable { dest.writeInt(eapType); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { // Note: this only validate the format of IMSI string itself. Additional verification // will be done by WifiService at the time of provisioning to verify against the IMSI // of the SIM card installed in the device. if (!verifyImsi()) { return false; } 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; } return true; } public static final Creator<SimCredential> CREATOR = new Creator<SimCredential>() { @Override Loading @@ -240,6 +367,43 @@ public final class Credential implements Parcelable { return new SimCredential[size]; } }; /** * Verify the IMSI (International Mobile Subscriber Identity) string. The string * should contain zero or more numeric digits, and might ends with a "*" for prefix * matching. * * @return true if IMSI is valid, false otherwise. */ private boolean verifyImsi() { if (TextUtils.isEmpty(imsi)) { Log.d(TAG, "Missing IMSI"); return false; } if (imsi.length() > MAX_IMSI_LENGTH) { Log.d(TAG, "IMSI exceeding maximum length: " + imsi.length()); return false; } // Locate the first non-digit character. int nonDigit; char stopChar = '\0'; for (nonDigit = 0; nonDigit < imsi.length(); nonDigit++) { stopChar = imsi.charAt(nonDigit); if (stopChar < '0' || stopChar > '9') { break; } } if (nonDigit == imsi.length()) { return true; } else if (nonDigit == imsi.length()-1 && stopChar == '*') { // Prefix matching. return true; } return false; } } public SimCredential simCredential = null; Loading Loading @@ -296,6 +460,42 @@ public final class Credential implements Parcelable { isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (TextUtils.isEmpty(realm)) { Log.d(TAG, "Missing realm"); return false; } if (realm.length() > MAX_REALM_LENGTH) { Log.d(TAG, "realm exceeding maximum length: " + realm.length()); return false; } // Verify the credential. if (userCredential != null) { if (!verifyUserCredential()) { return false; } } else if (certCredential != null) { if (!verifyCertCredential()) { return false; } } else if (simCredential != null) { if (!verifySimCredential()) { return false; } } else { Log.d(TAG, "Missing required credential"); return false; } return true; } public static final Creator<Credential> CREATOR = new Creator<Credential>() { @Override Loading @@ -317,6 +517,91 @@ public final class Credential implements Parcelable { } }; /** * Verify user credential. * * @return true if user credential is valid, false otherwise. */ private boolean verifyUserCredential() { if (userCredential == null) { Log.d(TAG, "Missing user credential"); return false; } if (certCredential != null || simCredential != null) { Log.d(TAG, "Contained more than one type of credential"); return false; } if (!userCredential.validate()) { return false; } if (caCertificate == null) { Log.d(TAG, "Missing CA Certificate for user credential"); return false; } return true; } /** * Verify certificate credential, which is used for EAP-TLS. This will verify * that the necessary client key and certificates are provided. * * @return true if certificate credential is valid, false otherwise. */ private boolean verifyCertCredential() { if (certCredential == null) { Log.d(TAG, "Missing certificate credential"); return false; } if (userCredential != null || simCredential != null) { Log.d(TAG, "Contained more than one type of credential"); return false; } if (!certCredential.validate()) { return false; } // Verify required key and certificates for certificate credential. if (caCertificate == null) { Log.d(TAG, "Missing CA Certificate for certificate credential"); return false; } if (clientPrivateKey == null) { Log.d(TAG, "Missing client private key for certificate credential"); return false; } try { // Verify SHA-256 fingerprint for client certificate. if (!verifySha256Fingerprint(clientCertificateChain, certCredential.certSha256FingerPrint)) { Log.d(TAG, "SHA-256 fingerprint mismatch"); return false; } } catch (NoSuchAlgorithmException | CertificateEncodingException e) { Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage()); return false; } return true; } /** * Verify SIM credential. * * @return true if SIM credential is valid, false otherwise. */ private boolean verifySimCredential() { if (simCredential == null) { Log.d(TAG, "Missing SIM credential"); return false; } if (userCredential != null || certCredential != null) { Log.d(TAG, "Contained more than one type of credential"); return false; } return simCredential.validate(); } private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) { if (key1 == null && key2 == null) { return true; Loading Loading @@ -373,4 +658,31 @@ public final class Credential implements Parcelable { return true; } /** * Verify that the digest for a certificate in the certificate chain matches expected * fingerprint. The certificate that matches the fingerprint is the client certificate. * * @param certChain Chain of certificates * @param expectedFingerprint The expected SHA-256 digest of the client certificate * @return true if the certificate chain contains a matching certificate, false otherwise * @throws NoSuchAlgorithmException * @throws CertificateEncodingException */ private static boolean verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint) throws NoSuchAlgorithmException, CertificateEncodingException { if (certChain == null) { return false; } MessageDigest digester = MessageDigest.getInstance("SHA-256"); for (X509Certificate certificate : certChain) { digester.reset(); byte[] fingerprint = digester.digest(certificate.getEncoded()); if (Arrays.equals(expectedFingerprint, fingerprint)) { return true; } } return false; } } wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java +20 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.net.wifi.hotspot2.pps; import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; import android.util.Log; import java.util.Arrays; Loading @@ -34,6 +35,8 @@ import java.util.Arrays; * @hide */ public final class HomeSP implements Parcelable { private static final String TAG = "HomeSP"; /** * FQDN (Fully Qualified Domain Name) of this home service provider. */ Loading Loading @@ -77,6 +80,23 @@ public final class HomeSP implements Parcelable { Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs); } /** * Validate HomeSP data. * * @return true on success or false on failure */ public boolean validate() { if (TextUtils.isEmpty(fqdn)) { Log.d(TAG, "Missing FQDN"); return false; } if (TextUtils.isEmpty(friendlyName)) { Log.d(TAG, "Missing friendly name"); return false; } return true; } public static final Creator<HomeSP> CREATOR = new Creator<HomeSP>() { @Override Loading wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +72 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,10 @@ package android.net.wifi.hotspot2; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.net.wifi.EAPConstants; import android.net.wifi.hotspot2.pps.Credential; import android.net.wifi.hotspot2.pps.HomeSP; import android.os.Parcel; Loading @@ -44,7 +46,9 @@ public class PasspointConfigurationTest { cred.realm = "realm"; cred.userCredential = null; cred.certCredential = null; cred.simCredential = null; cred.simCredential = new Credential.SimCredential(); cred.simCredential.imsi = "1234*"; cred.simCredential.eapType = EAPConstants.EAP_SIM; cred.caCertificate = null; cred.clientCertificateChain = null; cred.clientPrivateKey = null; Loading @@ -61,11 +65,20 @@ public class PasspointConfigurationTest { assertTrue(readConfig.equals(writeConfig)); } /** * Verify parcel read/write for a default configuration. * * @throws Exception */ @Test public void verifyParcelWithDefault() throws Exception { verifyParcel(new PasspointConfiguration()); } /** * Verify parcel read/write for a configuration that contained both HomeSP and Credential. * @throws Exception */ @Test public void verifyParcelWithHomeSPAndCredential() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); Loading @@ -74,6 +87,11 @@ public class PasspointConfigurationTest { verifyParcel(config); } /** * Verify parcel read/write for a configuration that contained only HomeSP. * * @throws Exception */ @Test public void verifyParcelWithHomeSPOnly() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); Loading @@ -81,10 +99,63 @@ public class PasspointConfigurationTest { verifyParcel(config); } /** * Verify parcel read/write for a configuration that contained only Credential. * * @throws Exception */ @Test public void verifyParcelWithCredentialOnly() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.credential = createCredential(); verifyParcel(config); } /** * Verify that a default/empty configuration is invalid. * * @throws Exception */ @Test public void validateDefaultConfig() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); assertFalse(config.validate()); } /** * Verify that a configuration without Credential is invalid. * * @throws Exception */ @Test public void validateConfigWithoutCredential() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.homeSp = createHomeSp(); assertFalse(config.validate()); } /** * Verify that a a configuration without HomeSP is invalid. * * @throws Exception */ @Test public void validateConfigWithoutHomeSp() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.credential = createCredential(); assertFalse(config.validate()); } /** * Verify a valid configuration. * * @throws Exception */ @Test public void validateValidConfig() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.homeSp = createHomeSp(); config.credential = createCredential(); assertTrue(config.validate()); } } No newline at end of file wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java +373 −5 File changed.Preview size limit exceeded, changes collapsed. Show changes Loading
wifi/java/android/net/wifi/hotspot2/PasspointConfiguration.java +15 −0 Original line number Diff line number Diff line Loading @@ -61,6 +61,21 @@ public final class PasspointConfiguration implements Parcelable { credential.equals(that.credential)); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (homeSp == null || !homeSp.validate()) { return false; } if (credential == null || !credential.validate()) { return false; } return true; } public static final Creator<PasspointConfiguration> CREATOR = new Creator<PasspointConfiguration>() { @Override Loading
wifi/java/android/net/wifi/hotspot2/pps/Credential.java +315 −3 Original line number Diff line number Diff line Loading @@ -16,15 +16,21 @@ package android.net.wifi.hotspot2.pps; import android.net.wifi.EAPConstants; import android.net.wifi.ParcelUtil; import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; import android.util.Log; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.cert.CertificateEncodingException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.HashSet; import java.util.Set; /** * Class representing Credential subtree in the PerProviderSubscription (PPS) Loading @@ -40,6 +46,14 @@ import java.util.Arrays; * @hide */ public final class Credential implements Parcelable { private static final String TAG = "Credential"; /** * 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; /** * The realm associated with this credential. It will be used to determine * if this credential can be used to authenticate with a given hotspot by Loading @@ -52,6 +66,26 @@ public final class Credential implements Parcelable { * Contains the fields under PerProviderSubscription/Credential/UsernamePassword subtree. */ public static final class UserCredential 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; /** * 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; /** * Supported Non-EAP inner methods. Refer to * Credential/UsernamePassword/EAPMethod/InnerEAPType in Hotspot 2.0 Release 2 Technical * Specification Section 9.1 for more info. */ private static final Set<String> SUPPORTED_AUTH = new HashSet<String>(Arrays.asList("PAP", "CHAP", "MS-CHAP", "MS-CHAP-V2")); /** * Username of the credential. */ Loading Loading @@ -104,6 +138,44 @@ public final class Credential implements Parcelable { TextUtils.equals(nonEapInnerMethod, that.nonEapInnerMethod); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (TextUtils.isEmpty(username)) { Log.d(TAG, "Missing username"); return false; } if (username.length() > MAX_USERNAME_LENGTH) { Log.d(TAG, "username exceeding maximum length: " + username.length()); return false; } if (TextUtils.isEmpty(password)) { Log.d(TAG, "Missing password"); return false; } if (password.length() > MAX_PASSWORD_LENGTH) { Log.d(TAG, "password exceeding maximum length: " + password.length()); return false; } // Only supports EAP-TTLS for user credential. if (eapType != EAPConstants.EAP_TTLS) { Log.d(TAG, "Invalid EAP Type for user credential: " + eapType); return false; } // Verify Non-EAP inner method for EAP-TTLS. if (!SUPPORTED_AUTH.contains(nonEapInnerMethod)) { Log.d(TAG, "Invalid non-EAP inner method for EAP-TTLS: " + nonEapInnerMethod); return false; } return true; } public static final Creator<UserCredential> CREATOR = new Creator<UserCredential>() { @Override Loading @@ -125,12 +197,22 @@ public final class Credential implements Parcelable { public UserCredential userCredential = null; /** * Certificate based credential. * Certificate based credential. This is used for EAP-TLS. * Contains fields under PerProviderSubscription/Credential/DigitalCertificate subtree. */ public static final class CertificateCredential implements Parcelable { /** * Certificate type. Valid values are "802.1ar" and "x509v3". * Supported certificate types. */ private static final String CERT_TYPE_X509V3 = "x509v3"; /** * Certificate SHA-256 fingerprint length. */ private static final int CERT_SHA256_FINGER_PRINT_LENGTH = 32; /** * Certificate type. */ public String certType = null; Loading Loading @@ -164,6 +246,24 @@ public final class Credential implements Parcelable { Arrays.equals(certSha256FingerPrint, that.certSha256FingerPrint); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (!TextUtils.equals(CERT_TYPE_X509V3, certType)) { Log.d(TAG, "Unsupported certificate type: " + certType); return false; } if (certSha256FingerPrint == null || certSha256FingerPrint.length != CERT_SHA256_FINGER_PRINT_LENGTH) { Log.d(TAG, "Invalid SHA-256 fingerprint"); return false; } return true; } public static final Creator<CertificateCredential> CREATOR = new Creator<CertificateCredential>() { @Override Loading @@ -188,7 +288,14 @@ public final class Credential implements Parcelable { */ public static final class SimCredential implements Parcelable { /** * International Mobile device Subscriber Identity. * Maximum string length for IMSI. */ public static final int MAX_IMSI_LENGTH = 15; /** * International Mobile Subscriber Identity, is used to identify the user * of a cellular network and is a unique identification associated with all * cellular networks */ public String imsi = null; Loading Loading @@ -225,6 +332,26 @@ public final class Credential implements Parcelable { dest.writeInt(eapType); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { // Note: this only validate the format of IMSI string itself. Additional verification // will be done by WifiService at the time of provisioning to verify against the IMSI // of the SIM card installed in the device. if (!verifyImsi()) { return false; } 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; } return true; } public static final Creator<SimCredential> CREATOR = new Creator<SimCredential>() { @Override Loading @@ -240,6 +367,43 @@ public final class Credential implements Parcelable { return new SimCredential[size]; } }; /** * Verify the IMSI (International Mobile Subscriber Identity) string. The string * should contain zero or more numeric digits, and might ends with a "*" for prefix * matching. * * @return true if IMSI is valid, false otherwise. */ private boolean verifyImsi() { if (TextUtils.isEmpty(imsi)) { Log.d(TAG, "Missing IMSI"); return false; } if (imsi.length() > MAX_IMSI_LENGTH) { Log.d(TAG, "IMSI exceeding maximum length: " + imsi.length()); return false; } // Locate the first non-digit character. int nonDigit; char stopChar = '\0'; for (nonDigit = 0; nonDigit < imsi.length(); nonDigit++) { stopChar = imsi.charAt(nonDigit); if (stopChar < '0' || stopChar > '9') { break; } } if (nonDigit == imsi.length()) { return true; } else if (nonDigit == imsi.length()-1 && stopChar == '*') { // Prefix matching. return true; } return false; } } public SimCredential simCredential = null; Loading Loading @@ -296,6 +460,42 @@ public final class Credential implements Parcelable { isPrivateKeyEquals(clientPrivateKey, that.clientPrivateKey); } /** * Validate the configuration data. * * @return true on success or false on failure */ public boolean validate() { if (TextUtils.isEmpty(realm)) { Log.d(TAG, "Missing realm"); return false; } if (realm.length() > MAX_REALM_LENGTH) { Log.d(TAG, "realm exceeding maximum length: " + realm.length()); return false; } // Verify the credential. if (userCredential != null) { if (!verifyUserCredential()) { return false; } } else if (certCredential != null) { if (!verifyCertCredential()) { return false; } } else if (simCredential != null) { if (!verifySimCredential()) { return false; } } else { Log.d(TAG, "Missing required credential"); return false; } return true; } public static final Creator<Credential> CREATOR = new Creator<Credential>() { @Override Loading @@ -317,6 +517,91 @@ public final class Credential implements Parcelable { } }; /** * Verify user credential. * * @return true if user credential is valid, false otherwise. */ private boolean verifyUserCredential() { if (userCredential == null) { Log.d(TAG, "Missing user credential"); return false; } if (certCredential != null || simCredential != null) { Log.d(TAG, "Contained more than one type of credential"); return false; } if (!userCredential.validate()) { return false; } if (caCertificate == null) { Log.d(TAG, "Missing CA Certificate for user credential"); return false; } return true; } /** * Verify certificate credential, which is used for EAP-TLS. This will verify * that the necessary client key and certificates are provided. * * @return true if certificate credential is valid, false otherwise. */ private boolean verifyCertCredential() { if (certCredential == null) { Log.d(TAG, "Missing certificate credential"); return false; } if (userCredential != null || simCredential != null) { Log.d(TAG, "Contained more than one type of credential"); return false; } if (!certCredential.validate()) { return false; } // Verify required key and certificates for certificate credential. if (caCertificate == null) { Log.d(TAG, "Missing CA Certificate for certificate credential"); return false; } if (clientPrivateKey == null) { Log.d(TAG, "Missing client private key for certificate credential"); return false; } try { // Verify SHA-256 fingerprint for client certificate. if (!verifySha256Fingerprint(clientCertificateChain, certCredential.certSha256FingerPrint)) { Log.d(TAG, "SHA-256 fingerprint mismatch"); return false; } } catch (NoSuchAlgorithmException | CertificateEncodingException e) { Log.d(TAG, "Failed to verify SHA-256 fingerprint: " + e.getMessage()); return false; } return true; } /** * Verify SIM credential. * * @return true if SIM credential is valid, false otherwise. */ private boolean verifySimCredential() { if (simCredential == null) { Log.d(TAG, "Missing SIM credential"); return false; } if (userCredential != null || certCredential != null) { Log.d(TAG, "Contained more than one type of credential"); return false; } return simCredential.validate(); } private static boolean isPrivateKeyEquals(PrivateKey key1, PrivateKey key2) { if (key1 == null && key2 == null) { return true; Loading Loading @@ -373,4 +658,31 @@ public final class Credential implements Parcelable { return true; } /** * Verify that the digest for a certificate in the certificate chain matches expected * fingerprint. The certificate that matches the fingerprint is the client certificate. * * @param certChain Chain of certificates * @param expectedFingerprint The expected SHA-256 digest of the client certificate * @return true if the certificate chain contains a matching certificate, false otherwise * @throws NoSuchAlgorithmException * @throws CertificateEncodingException */ private static boolean verifySha256Fingerprint(X509Certificate[] certChain, byte[] expectedFingerprint) throws NoSuchAlgorithmException, CertificateEncodingException { if (certChain == null) { return false; } MessageDigest digester = MessageDigest.getInstance("SHA-256"); for (X509Certificate certificate : certChain) { digester.reset(); byte[] fingerprint = digester.digest(certificate.getEncoded()); if (Arrays.equals(expectedFingerprint, fingerprint)) { return true; } } return false; } }
wifi/java/android/net/wifi/hotspot2/pps/HomeSP.java +20 −0 Original line number Diff line number Diff line Loading @@ -19,6 +19,7 @@ package android.net.wifi.hotspot2.pps; import android.os.Parcelable; import android.os.Parcel; import android.text.TextUtils; import android.util.Log; import java.util.Arrays; Loading @@ -34,6 +35,8 @@ import java.util.Arrays; * @hide */ public final class HomeSP implements Parcelable { private static final String TAG = "HomeSP"; /** * FQDN (Fully Qualified Domain Name) of this home service provider. */ Loading Loading @@ -77,6 +80,23 @@ public final class HomeSP implements Parcelable { Arrays.equals(roamingConsortiumOIs, that.roamingConsortiumOIs); } /** * Validate HomeSP data. * * @return true on success or false on failure */ public boolean validate() { if (TextUtils.isEmpty(fqdn)) { Log.d(TAG, "Missing FQDN"); return false; } if (TextUtils.isEmpty(friendlyName)) { Log.d(TAG, "Missing friendly name"); return false; } return true; } public static final Creator<HomeSP> CREATOR = new Creator<HomeSP>() { @Override Loading
wifi/tests/src/android/net/wifi/hotspot2/PasspointConfigurationTest.java +72 −1 Original line number Diff line number Diff line Loading @@ -16,8 +16,10 @@ package android.net.wifi.hotspot2; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import android.net.wifi.EAPConstants; import android.net.wifi.hotspot2.pps.Credential; import android.net.wifi.hotspot2.pps.HomeSP; import android.os.Parcel; Loading @@ -44,7 +46,9 @@ public class PasspointConfigurationTest { cred.realm = "realm"; cred.userCredential = null; cred.certCredential = null; cred.simCredential = null; cred.simCredential = new Credential.SimCredential(); cred.simCredential.imsi = "1234*"; cred.simCredential.eapType = EAPConstants.EAP_SIM; cred.caCertificate = null; cred.clientCertificateChain = null; cred.clientPrivateKey = null; Loading @@ -61,11 +65,20 @@ public class PasspointConfigurationTest { assertTrue(readConfig.equals(writeConfig)); } /** * Verify parcel read/write for a default configuration. * * @throws Exception */ @Test public void verifyParcelWithDefault() throws Exception { verifyParcel(new PasspointConfiguration()); } /** * Verify parcel read/write for a configuration that contained both HomeSP and Credential. * @throws Exception */ @Test public void verifyParcelWithHomeSPAndCredential() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); Loading @@ -74,6 +87,11 @@ public class PasspointConfigurationTest { verifyParcel(config); } /** * Verify parcel read/write for a configuration that contained only HomeSP. * * @throws Exception */ @Test public void verifyParcelWithHomeSPOnly() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); Loading @@ -81,10 +99,63 @@ public class PasspointConfigurationTest { verifyParcel(config); } /** * Verify parcel read/write for a configuration that contained only Credential. * * @throws Exception */ @Test public void verifyParcelWithCredentialOnly() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.credential = createCredential(); verifyParcel(config); } /** * Verify that a default/empty configuration is invalid. * * @throws Exception */ @Test public void validateDefaultConfig() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); assertFalse(config.validate()); } /** * Verify that a configuration without Credential is invalid. * * @throws Exception */ @Test public void validateConfigWithoutCredential() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.homeSp = createHomeSp(); assertFalse(config.validate()); } /** * Verify that a a configuration without HomeSP is invalid. * * @throws Exception */ @Test public void validateConfigWithoutHomeSp() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.credential = createCredential(); assertFalse(config.validate()); } /** * Verify a valid configuration. * * @throws Exception */ @Test public void validateValidConfig() throws Exception { PasspointConfiguration config = new PasspointConfiguration(); config.homeSp = createHomeSp(); config.credential = createCredential(); assertTrue(config.validate()); } } No newline at end of file
wifi/tests/src/android/net/wifi/hotspot2/pps/CredentialTest.java +373 −5 File changed.Preview size limit exceeded, changes collapsed. Show changes