diff --git a/api/system-current.txt b/api/system-current.txt index 1146522dafeac..01fe90df492a3 100755 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -5739,6 +5739,7 @@ package android.net.wifi { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int SOFTAP_FEATURE_ACS_OFFLOAD = 1; // 0x1 field public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 2; // 0x2 + field public static final int SOFTAP_FEATURE_WPA3_SAE = 4; // 0x4 } public final class SoftApConfiguration implements android.os.Parcelable { @@ -5747,6 +5748,7 @@ package android.net.wifi { method @Nullable public android.net.MacAddress getBssid(); method public int getChannel(); method public int getMaxNumberOfClients(); + method @Nullable public String getPassphrase(); method public int getSecurityType(); method @Nullable public String getSsid(); method @Nullable public String getWpa2Passphrase(); @@ -5759,6 +5761,8 @@ package android.net.wifi { field @NonNull public static final android.os.Parcelable.Creator CREATOR; field public static final int SECURITY_TYPE_OPEN = 0; // 0x0 field public static final int SECURITY_TYPE_WPA2_PSK = 1; // 0x1 + field public static final int SECURITY_TYPE_WPA3_SAE = 3; // 0x3 + field public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; // 0x2 } public static final class SoftApConfiguration.Builder { @@ -5770,6 +5774,7 @@ package android.net.wifi { method @NonNull public android.net.wifi.SoftApConfiguration.Builder setChannel(int, int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setHiddenSsid(boolean); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setMaxNumberOfClients(int); + method @NonNull public android.net.wifi.SoftApConfiguration.Builder setPassphrase(@Nullable String, int); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setSsid(@Nullable String); method @NonNull public android.net.wifi.SoftApConfiguration.Builder setWpa2Passphrase(@Nullable String); } diff --git a/wifi/java/android/net/wifi/SoftApCapability.java b/wifi/java/android/net/wifi/SoftApCapability.java index c4474e2bc9cc1..2bbe7d2aa4ec5 100644 --- a/wifi/java/android/net/wifi/SoftApCapability.java +++ b/wifi/java/android/net/wifi/SoftApCapability.java @@ -61,11 +61,20 @@ public final class SoftApCapability implements Parcelable { */ public static final int SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT = 1 << 1; + + /** + * Support for WPA3 Simultaneous Authentication of Equals (WPA3-SAE). + * + * flag when {@link config_wifi_softap_sae_supported)} is true. + */ + public static final int SOFTAP_FEATURE_WPA3_SAE = 1 << 2; + /** @hide */ @Retention(RetentionPolicy.SOURCE) @IntDef(flag = true, prefix = { "SOFTAP_FEATURE_" }, value = { SOFTAP_FEATURE_ACS_OFFLOAD, SOFTAP_FEATURE_CLIENT_FORCE_DISCONNECT, + SOFTAP_FEATURE_WPA3_SAE, }) public @interface HotspotFeatures {} diff --git a/wifi/java/android/net/wifi/SoftApConfiguration.java b/wifi/java/android/net/wifi/SoftApConfiguration.java index 05e245b8eabc0..65e9b7910e5e8 100644 --- a/wifi/java/android/net/wifi/SoftApConfiguration.java +++ b/wifi/java/android/net/wifi/SoftApConfiguration.java @@ -25,6 +25,7 @@ import android.os.Parcel; import android.os.Parcelable; import android.text.TextUtils; +import com.android.internal.annotations.VisibleForTesting; import com.android.internal.util.Preconditions; import java.lang.annotation.Retention; @@ -55,6 +56,11 @@ import java.util.concurrent.Executor; @SystemApi public final class SoftApConfiguration implements Parcelable { + @VisibleForTesting + static final int PSK_MIN_LEN = 8; + @VisibleForTesting + static final int PSK_MAX_LEN = 63; + /** * 2GHz band. * @hide @@ -142,9 +148,10 @@ public final class SoftApConfiguration implements Parcelable { private final @Nullable MacAddress mBssid; /** - * Pre-shared key for WPA2-PSK encryption (non-null enables WPA2-PSK). + * Pre-shared key for WPA2-PSK or WPA3-SAE-Transition or WPA3-SAE encryption which depends on + * the security type. */ - private final @Nullable String mWpa2Passphrase; + private final @Nullable String mPassphrase; /** * This is a network that does not broadcast its SSID, so an @@ -185,21 +192,31 @@ public final class SoftApConfiguration implements Parcelable { @SystemApi public static final int SECURITY_TYPE_WPA2_PSK = 1; + /** @hide */ + @SystemApi + public static final int SECURITY_TYPE_WPA3_SAE_TRANSITION = 2; + + /** @hide */ + @SystemApi + public static final int SECURITY_TYPE_WPA3_SAE = 3; + /** @hide */ @Retention(RetentionPolicy.SOURCE) - @IntDef(prefix = { "SECURITY_TYPE" }, value = { + @IntDef(prefix = { "SECURITY_TYPE_" }, value = { SECURITY_TYPE_OPEN, SECURITY_TYPE_WPA2_PSK, + SECURITY_TYPE_WPA3_SAE_TRANSITION, + SECURITY_TYPE_WPA3_SAE, }) public @interface SecurityType {} /** Private constructor for Builder and Parcelable implementation. */ private SoftApConfiguration(@Nullable String ssid, @Nullable MacAddress bssid, - @Nullable String wpa2Passphrase, boolean hiddenSsid, @BandType int band, int channel, + @Nullable String passphrase, boolean hiddenSsid, @BandType int band, int channel, @SecurityType int securityType, int maxNumberOfClients) { mSsid = ssid; mBssid = bssid; - mWpa2Passphrase = wpa2Passphrase; + mPassphrase = passphrase; mHiddenSsid = hiddenSsid; mBand = band; mChannel = channel; @@ -218,7 +235,7 @@ public final class SoftApConfiguration implements Parcelable { SoftApConfiguration other = (SoftApConfiguration) otherObj; return Objects.equals(mSsid, other.mSsid) && Objects.equals(mBssid, other.mBssid) - && Objects.equals(mWpa2Passphrase, other.mWpa2Passphrase) + && Objects.equals(mPassphrase, other.mPassphrase) && mHiddenSsid == other.mHiddenSsid && mBand == other.mBand && mChannel == other.mChannel @@ -228,7 +245,7 @@ public final class SoftApConfiguration implements Parcelable { @Override public int hashCode() { - return Objects.hash(mSsid, mBssid, mWpa2Passphrase, mHiddenSsid, + return Objects.hash(mSsid, mBssid, mPassphrase, mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients); } @@ -237,8 +254,8 @@ public final class SoftApConfiguration implements Parcelable { StringBuilder sbuf = new StringBuilder(); sbuf.append("ssid=").append(mSsid); if (mBssid != null) sbuf.append(" \n bssid=").append(mBssid.toString()); - sbuf.append(" \n Wpa2Passphrase =").append( - TextUtils.isEmpty(mWpa2Passphrase) ? "" : ""); + sbuf.append(" \n Passphrase =").append( + TextUtils.isEmpty(mPassphrase) ? "" : ""); sbuf.append(" \n HiddenSsid =").append(mHiddenSsid); sbuf.append(" \n Band =").append(mBand); sbuf.append(" \n Channel =").append(mChannel); @@ -251,7 +268,7 @@ public final class SoftApConfiguration implements Parcelable { public void writeToParcel(@NonNull Parcel dest, int flags) { dest.writeString(mSsid); dest.writeParcelable(mBssid, flags); - dest.writeString(mWpa2Passphrase); + dest.writeString(mPassphrase); dest.writeBoolean(mHiddenSsid); dest.writeInt(mBand); dest.writeInt(mChannel); @@ -299,13 +316,26 @@ public final class SoftApConfiguration implements Parcelable { return mBssid; } + // TODO: Remove it after update the caller /** * Returns String set to be passphrase for the WPA2-PSK AP. - * {@link Builder#setWpa2Passphrase(String)}. + * {@link #setWpa2Passphrase(String)}. */ @Nullable public String getWpa2Passphrase() { - return mWpa2Passphrase; + if (mSecurityType == SECURITY_TYPE_WPA2_PSK) { + return mPassphrase; + } + return null; + } + + /** + * Returns String set to be passphrase for current AP. + * {@link #setPassphrase(String, @SecurityType int)}. + */ + @Nullable + public String getPassphrase() { + return mPassphrase; } /** @@ -360,23 +390,12 @@ public final class SoftApConfiguration implements Parcelable { public static final class Builder { private String mSsid; private MacAddress mBssid; - private String mWpa2Passphrase; + private String mPassphrase; private boolean mHiddenSsid; private int mBand; private int mChannel; private int mMaxNumberOfClients; - - private int setSecurityType() { - int securityType = SECURITY_TYPE_OPEN; - if (!TextUtils.isEmpty(mWpa2Passphrase)) { // WPA2-PSK network. - securityType = SECURITY_TYPE_WPA2_PSK; - } - return securityType; - } - - private void clearAllPassphrase() { - mWpa2Passphrase = null; - } + private int mSecurityType; /** * Constructs a Builder with default values (see {@link Builder}). @@ -384,11 +403,12 @@ public final class SoftApConfiguration implements Parcelable { public Builder() { mSsid = null; mBssid = null; - mWpa2Passphrase = null; + mPassphrase = null; mHiddenSsid = false; mBand = BAND_2GHZ; mChannel = 0; mMaxNumberOfClients = 0; + mSecurityType = SECURITY_TYPE_OPEN; } /** @@ -399,11 +419,12 @@ public final class SoftApConfiguration implements Parcelable { mSsid = other.mSsid; mBssid = other.mBssid; - mWpa2Passphrase = other.mWpa2Passphrase; + mPassphrase = other.mPassphrase; mHiddenSsid = other.mHiddenSsid; mBand = other.mBand; mChannel = other.mChannel; mMaxNumberOfClients = other.mMaxNumberOfClients; + mSecurityType = other.mSecurityType; } /** @@ -413,8 +434,8 @@ public final class SoftApConfiguration implements Parcelable { */ @NonNull public SoftApConfiguration build() { - return new SoftApConfiguration(mSsid, mBssid, mWpa2Passphrase, - mHiddenSsid, mBand, mChannel, setSecurityType(), mMaxNumberOfClients); + return new SoftApConfiguration(mSsid, mBssid, mPassphrase, + mHiddenSsid, mBand, mChannel, mSecurityType, mMaxNumberOfClients); } /** @@ -461,6 +482,7 @@ public final class SoftApConfiguration implements Parcelable { return this; } + // TODO: Remove it after update the caller /** * Specifies that this AP should use WPA2-PSK with the given ASCII WPA2 passphrase. * When set to null, an open network is created. @@ -473,15 +495,47 @@ public final class SoftApConfiguration implements Parcelable { */ @NonNull public Builder setWpa2Passphrase(@Nullable String passphrase) { - if (passphrase != null) { + return setPassphrase(passphrase, SECURITY_TYPE_WPA2_PSK); + } + + /** + * Specifies that this AP should use specific security type with the given ASCII passphrase. + * + * @param securityType one of the security types from {@link @SecurityType}. + * @param passphrase The passphrase to use for sepcific {@link @SecurityType} configuration + * or null with {@link @SecurityType#SECURITY_TYPE_OPEN}. + * + * @return Builder for chaining. + * @throws IllegalArgumentException when the passphrase length is invalid and + * {@code securityType} is not {@link @SecurityType#SECURITY_TYPE_OPEN} + * or non-null passphrase and {@code securityType} is + * {@link @SecurityType#SECURITY_TYPE_OPEN}. + */ + @NonNull + public Builder setPassphrase(@Nullable String passphrase, @SecurityType int securityType) { + if (securityType == SECURITY_TYPE_OPEN) { + if (passphrase != null) { + throw new IllegalArgumentException( + "passphrase should be null when security type is open"); + } + } else { + Preconditions.checkStringNotEmpty(passphrase); final CharsetEncoder asciiEncoder = StandardCharsets.US_ASCII.newEncoder(); if (!asciiEncoder.canEncode(passphrase)) { throw new IllegalArgumentException("passphrase not ASCII encodable"); } - Preconditions.checkStringNotEmpty(passphrase); + if (securityType == SECURITY_TYPE_WPA2_PSK + || securityType == SECURITY_TYPE_WPA3_SAE_TRANSITION) { + if (passphrase.length() < PSK_MIN_LEN || passphrase.length() > PSK_MAX_LEN) { + throw new IllegalArgumentException( + "Password size must be at least " + PSK_MIN_LEN + + " and no more than " + PSK_MAX_LEN + + " for WPA2_PSK and WPA3_SAE_TRANSITION Mode"); + } + } } - clearAllPassphrase(); - mWpa2Passphrase = passphrase; + mSecurityType = securityType; + mPassphrase = passphrase; return this; } diff --git a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java index 1f601036a718c..acd3343558062 100644 --- a/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java +++ b/wifi/tests/src/android/net/wifi/SoftApConfigurationTest.java @@ -25,8 +25,12 @@ import androidx.test.filters.SmallTest; import org.junit.Test; +import java.util.Random; + @SmallTest public class SoftApConfigurationTest { + private static final String TEST_CHAR_SET_AS_STRING = "abcdefghijklmnopqrstuvwxyz0123456789"; + private SoftApConfiguration parcelUnparcel(SoftApConfiguration configIn) { Parcel parcel = Parcel.obtain(); parcel.writeParcelable(configIn, 0); @@ -37,6 +41,25 @@ public class SoftApConfigurationTest { return configOut; } + /** + * Helper method to generate random string. + * + * Note: this method has limited use as a random string generator. + * The characters used in this method do no not cover all valid inputs. + * @param length number of characters to generate for the string + * @return String generated string of random characters + */ + private String generateRandomString(int length) { + Random random = new Random(); + StringBuilder stringBuilder = new StringBuilder(length); + int index = -1; + while (stringBuilder.length() < length) { + index = random.nextInt(TEST_CHAR_SET_AS_STRING.length()); + stringBuilder.append(TEST_CHAR_SET_AS_STRING.charAt(index)); + } + return stringBuilder.toString(); + } + @Test public void testBasicSettings() { SoftApConfiguration original = new SoftApConfiguration.Builder() @@ -45,7 +68,7 @@ public class SoftApConfigurationTest { .build(); assertThat(original.getSsid()).isEqualTo("ssid"); assertThat(original.getBssid()).isEqualTo(MacAddress.fromString("11:22:33:44:55:66")); - assertThat(original.getWpa2Passphrase()).isNull(); + assertThat(original.getPassphrase()).isNull(); assertThat(original.getSecurityType()).isEqualTo(SoftApConfiguration.SECURITY_TYPE_OPEN); assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ); assertThat(original.getChannel()).isEqualTo(0); @@ -66,9 +89,9 @@ public class SoftApConfigurationTest { @Test public void testWpa2() { SoftApConfiguration original = new SoftApConfiguration.Builder() - .setWpa2Passphrase("secretsecret") + .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .build(); - assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret"); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_2GHZ); @@ -90,13 +113,12 @@ public class SoftApConfigurationTest { @Test public void testWpa2WithAllFieldCustomized() { SoftApConfiguration original = new SoftApConfiguration.Builder() - .setWpa2Passphrase("secretsecret") - .setBand(SoftApConfiguration.BAND_ANY) + .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) .setChannel(149, SoftApConfiguration.BAND_5GHZ) .setHiddenSsid(true) .setMaxNumberOfClients(10) .build(); - assertThat(original.getWpa2Passphrase()).isEqualTo("secretsecret"); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); assertThat(original.getSecurityType()).isEqualTo( SoftApConfiguration.SECURITY_TYPE_WPA2_PSK); assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ); @@ -114,4 +136,98 @@ public class SoftApConfigurationTest { assertThat(copy).isEqualTo(original); assertThat(copy.hashCode()).isEqualTo(original.hashCode()); } + + @Test + public void testWpa3Sae() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", SoftApConfiguration.SECURITY_TYPE_WPA3_SAE) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); + assertThat(original.getSecurityType()).isEqualTo( + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE); + assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ); + assertThat(original.getChannel()).isEqualTo(149); + assertThat(original.isHiddenSsid()).isEqualTo(true); + + + SoftApConfiguration unparceled = parcelUnparcel(original); + assertThat(unparceled).isNotSameAs(original); + assertThat(unparceled).isEqualTo(original); + assertThat(unparceled.hashCode()).isEqualTo(original.hashCode()); + + SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build(); + assertThat(copy).isNotSameAs(original); + assertThat(copy).isEqualTo(original); + assertThat(copy.hashCode()).isEqualTo(original.hashCode()); + } + + @Test + public void testWpa3SaeTransition() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase("secretsecret", + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + assertThat(original.getSecurityType()).isEqualTo( + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION); + assertThat(original.getPassphrase()).isEqualTo("secretsecret"); + assertThat(original.getBand()).isEqualTo(SoftApConfiguration.BAND_5GHZ); + assertThat(original.getChannel()).isEqualTo(149); + assertThat(original.isHiddenSsid()).isEqualTo(true); + + + SoftApConfiguration unparceled = parcelUnparcel(original); + assertThat(unparceled).isNotSameAs(original); + assertThat(unparceled).isEqualTo(original); + assertThat(unparceled.hashCode()).isEqualTo(original.hashCode()); + + SoftApConfiguration copy = new SoftApConfiguration.Builder(original).build(); + assertThat(copy).isNotSameAs(original); + assertThat(copy).isEqualTo(original); + assertThat(copy.hashCode()).isEqualTo(original.hashCode()); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidShortPasswordLengthForWpa2() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1), + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidLongPasswordLengthForWpa2() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1), + SoftApConfiguration.SECURITY_TYPE_WPA2_PSK) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidShortPasswordLengthForWpa3SaeTransition() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MIN_LEN - 1), + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + + @Test(expected = IllegalArgumentException.class) + public void testInvalidLongPasswordLengthForWpa3SaeTransition() { + SoftApConfiguration original = new SoftApConfiguration.Builder() + .setPassphrase(generateRandomString(SoftApConfiguration.PSK_MAX_LEN + 1), + SoftApConfiguration.SECURITY_TYPE_WPA3_SAE_TRANSITION) + .setChannel(149, SoftApConfiguration.BAND_5GHZ) + .setHiddenSsid(true) + .build(); + } + }