From 77ea7bf52f2f08066ac74f49478301e81f579f31 Mon Sep 17 00:00:00 2001 From: xshu Date: Thu, 12 Sep 2019 11:31:48 -0700 Subject: [MAC rand] Removing persistent storage Will now calculate the randomized MAC address directly for new wifi networks. For existing networks that already have saved randomized MAC address, we will continue to use the saved MAC. Bug: 140065828 Test: unit tests before and after factory reset Test: Verified that new MAC address entries are not populated into WifiConfigStore.xml Test: Manually verified calculated MAC addresses are consistent and valid. Test: Manually verified that the device secret is persisted over different builds Change-Id: Ie216fbaf33e2582ae1a0030c5046b0d4162255d9 Merged-In: I4b4e2cc6fe304d277661c4743e4fb86bb7500f16 (cherry picked from commit I4b4e2cc6fe304d277661c4743e4fb86bb7500f16) --- .../com/android/server/wifi/WifiConfigManager.java | 108 +++++++++++++++------ .../android/server/wifi/WifiConfigurationUtil.java | 102 +++++++++++++++++++ .../android/server/wifi/WifiConfigManagerTest.java | 18 +++- .../server/wifi/WifiConfigurationUtilTest.java | 30 ++++++ 4 files changed, 225 insertions(+), 33 deletions(-) diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java index 683ace4c8..d1734d445 100644 --- a/service/java/com/android/server/wifi/WifiConfigManager.java +++ b/service/java/com/android/server/wifi/WifiConfigManager.java @@ -76,6 +76,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.crypto.Mac; + /** * This class provides the APIs to manage configured Wi-Fi networks. * It deals with the following: @@ -228,6 +230,9 @@ public class WifiConfigManager { private static final int WIFI_PNO_FREQUENCY_CULLING_ENABLED_DEFAULT = 1; // 0 = disabled private static final int WIFI_PNO_RECENCY_SORTING_ENABLED_DEFAULT = 1; // 0 = disabled: + private static final MacAddress DEFAULT_MAC_ADDRESS = + MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); + /** * Expiration timeout for deleted ephemeral ssids. (1 day) */ @@ -272,6 +277,7 @@ public class WifiConfigManager { private final WifiPermissionsWrapper mWifiPermissionsWrapper; private final WifiInjector mWifiInjector; private boolean mConnectedMacRandomzationSupported; + private final Mac mMac; /** * Local log used for debugging any WifiConfigManager issues. @@ -446,6 +452,11 @@ public class WifiConfigManager { } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to resolve SystemUI's UID."); } + mMac = WifiConfigurationUtil.obtainMacRandHashFunction(Process.WIFI_UID); + if (mMac == null) { + Log.wtf(TAG, "Failed to obtain secret for MAC randomization." + + " All randomized MAC addresses are lost!"); + } } /** @@ -464,6 +475,59 @@ public class WifiConfigManager { return sb.toString(); } + @VisibleForTesting + protected int getRandomizedMacAddressMappingSize() { + return mRandomizedMacAddressMapping.size(); + } + + /** + * The persistent randomized MAC address is locally generated for each SSID and does not + * change until factory reset of the device. In the initial Q release the per-SSID randomized + * MAC is saved on the device, but in an update the storing of randomized MAC is removed. + * Instead, the randomized MAC is calculated directly from the SSID and a on device secret. + * For backward compatibility, this method first checks the device storage for saved + * randomized MAC. If it is not found or the saved MAC is invalid then it will calculate the + * randomized MAC directly. + * + * In the future as devices launched on Q no longer get supported, this method should get + * simplified to return the calculated MAC address directly. + * @param config the WifiConfiguration to obtain MAC address for. + * @return persistent MAC address for this WifiConfiguration + */ + private MacAddress getPersistentMacAddress(WifiConfiguration config) { + // mRandomizedMacAddressMapping had been the location to save randomized MAC addresses. + String persistentMacString = mRandomizedMacAddressMapping.get( + config.getSsidAndSecurityTypeString()); + // Use the MAC address stored in the storage if it exists and is valid. Otherwise + // use the MAC address calculated from a hash function as the persistent MAC. + if (persistentMacString != null) { + try { + return MacAddress.fromString(persistentMacString); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Error creating randomized MAC address from stored value."); + mRandomizedMacAddressMapping.remove(config.getSsidAndSecurityTypeString()); + } + } + return WifiConfigurationUtil.calculatePersistentMacForConfiguration(config, mMac); + } + + /** + * Obtain the persistent MAC address by first reading from an internal database. If non exists + * then calculate the persistent MAC using HMAC-SHA256. + * Finally set the randomized MAC of the configuration to the randomized MAC obtained. + * @param config the WifiConfiguration to make the update + * @return the persistent MacAddress or null if the operation is unsuccessful + */ + private MacAddress setRandomizedMacToPersistentMac(WifiConfiguration config) { + MacAddress persistentMac = getPersistentMacAddress(config); + if (persistentMac == null || persistentMac.equals(config.getRandomizedMacAddress())) { + return persistentMac; + } + WifiConfiguration internalConfig = getInternalConfiguredNetwork(config.networkId); + internalConfig.setRandomizedMacAddress(persistentMac); + return persistentMac; + } + /** * Enable/disable verbose logging in WifiConfigManager & its helper classes. */ @@ -520,8 +584,7 @@ public class WifiConfigManager { * @param configuration WifiConfiguration to hide the MAC address */ private void maskRandomizedMacAddressInWifiConfiguration(WifiConfiguration configuration) { - MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); - configuration.setRandomizedMacAddress(defaultMac); + configuration.setRandomizedMacAddress(DEFAULT_MAC_ADDRESS); } /** @@ -1050,34 +1113,11 @@ public class WifiConfigManager { packageName != null ? packageName : mContext.getPackageManager().getNameForUid(uid); newInternalConfig.creationTime = newInternalConfig.updateTime = createDebugTimeStampString(mClock.getWallClockMillis()); - updateRandomizedMacAddress(newInternalConfig); - - return newInternalConfig; - } - - /** - * Sets the randomized address for the given configuration from stored map if it exist. - * Otherwise generates a new randomized address and save to the stored map. - * @param config - */ - private void updateRandomizedMacAddress(WifiConfiguration config) { - // Update randomized MAC address according to stored map - final String key = config.getSsidAndSecurityTypeString(); - // If the key is not found in the current store, then it means this network has never been - // seen before. So add it to store. - if (!mRandomizedMacAddressMapping.containsKey(key)) { - mRandomizedMacAddressMapping.put(key, - config.getOrCreateRandomizedMacAddress().toString()); - } else { // Otherwise read from the store and set the WifiConfiguration - try { - config.setRandomizedMacAddress( - MacAddress.fromString(mRandomizedMacAddressMapping.get(key))); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Error creating randomized MAC address from stored value."); - mRandomizedMacAddressMapping.put(key, - config.getOrCreateRandomizedMacAddress().toString()); - } + MacAddress randomizedMac = getPersistentMacAddress(newInternalConfig); + if (randomizedMac != null) { + newInternalConfig.setRandomizedMacAddress(randomizedMac); } + return newInternalConfig; } /** @@ -3026,12 +3066,16 @@ public class WifiConfigManager { } /** - * Generate randomized MAC addresses for configured networks and persist mapping to storage. + * Assign randomized MAC addresses for configured networks. + * This is needed to generate persistent randomized MAC address for existing networks when + * a device updates to Q+ for the first time since we are not calling addOrUpdateNetwork when + * we load configuration at boot. */ private void generateRandomizedMacAddresses() { for (WifiConfiguration config : getInternalConfiguredNetworks()) { - mRandomizedMacAddressMapping.put(config.getSsidAndSecurityTypeString(), - config.getOrCreateRandomizedMacAddress().toString()); + if (DEFAULT_MAC_ADDRESS.equals(config.getRandomizedMacAddress())) { + setRandomizedMacToPersistentMac(config); + } } } diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java index 59d3eb3f4..b8992a011 100644 --- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java +++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java @@ -28,6 +28,9 @@ import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; import android.os.PatternMatcher; import android.os.UserHandle; +import android.security.keystore.AndroidKeyStoreProvider; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -36,7 +39,17 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.TelephonyUtil; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.ProviderException; +import java.security.UnrecoverableKeyException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.BitSet; @@ -44,6 +57,10 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; + /** * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations. * Currently contains: @@ -72,6 +89,10 @@ public class WifiConfigurationUtil { new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair MATCH_ALL_BSSID_PATTERN = new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + private static final String MAC_RANDOMIZATION_ALIAS = "MacRandSecret"; + private static final long MAC_ADDRESS_VALID_LONG_MASK = (1L << 48) - 1; + private static final long MAC_ADDRESS_LOCALLY_ASSIGNED_MASK = 1L << 41; + private static final long MAC_ADDRESS_MULTICAST_MASK = 1L << 40; /** * Check whether a network configuration is visible to a user or any of its managed profiles. @@ -226,6 +247,87 @@ public class WifiConfigurationUtil { return newConfig.macRandomizationSetting != existingConfig.macRandomizationSetting; } + /** + * Computes the persistent randomized MAC of the given configuration using the given + * hash function. + * @param config the WifiConfiguration to compute MAC address for + * @param hashFunction the hash function that will perform the MAC address computation. + * @return The persistent randomized MAC address or null if inputs are invalid. + */ + public static MacAddress calculatePersistentMacForConfiguration(WifiConfiguration config, + Mac hashFunction) { + if (config == null || hashFunction == null) { + return null; + } + byte[] hashedBytes = hashFunction.doFinal( + config.getSsidAndSecurityTypeString().getBytes(StandardCharsets.UTF_8)); + ByteBuffer bf = ByteBuffer.wrap(hashedBytes); + long longFromSsid = bf.getLong(); + /** + * Masks the generated long so that it represents a valid randomized MAC address. + * Specifically, this sets the locally assigned bit to 1, multicast bit to 0 + */ + longFromSsid &= MAC_ADDRESS_VALID_LONG_MASK; + longFromSsid |= MAC_ADDRESS_LOCALLY_ASSIGNED_MASK; + longFromSsid &= ~MAC_ADDRESS_MULTICAST_MASK; + bf.clear(); + bf.putLong(0, longFromSsid); + + // MacAddress.fromBytes requires input of length 6, which is obtained from the + // last 6 bytes from the generated long. + MacAddress macAddress = MacAddress.fromBytes(Arrays.copyOfRange(bf.array(), 2, 8)); + return macAddress; + } + + /** + * Retrieves a Hash function that could be used to calculate the persistent randomized MAC + * for a WifiConfiguration. + * @param uid the UID of the KeyStore to get the secret of the hash function from. + */ + public static Mac obtainMacRandHashFunction(int uid) { + try { + KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(uid); + // tries to retrieve the secret, and generate a new one if it's unavailable. + Key key = keyStore.getKey(MAC_RANDOMIZATION_ALIAS, null); + if (key == null) { + key = generateAndPersistNewMacRandomizationSecret(uid); + } + if (key == null) { + Log.e(TAG, "Failed to generate secret for " + MAC_RANDOMIZATION_ALIAS); + return null; + } + Mac result = Mac.getInstance("HmacSHA256"); + result.init(key); + return result; + } catch (KeyStoreException | NoSuchAlgorithmException | InvalidKeyException + | UnrecoverableKeyException | NoSuchProviderException e) { + Log.e(TAG, "Failure in obtainMacRandHashFunction", e); + return null; + } + } + + /** + * Generates and returns a secret key to use for Mac randomization. + * Will also persist the generated secret inside KeyStore, accessible in the + * future with KeyGenerator#getKey. + */ + private static SecretKey generateAndPersistNewMacRandomizationSecret(int uid) { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore"); + keyGenerator.init( + new KeyGenParameterSpec.Builder(MAC_RANDOMIZATION_ALIAS, + KeyProperties.PURPOSE_SIGN) + .setUid(uid) + .build()); + return keyGenerator.generateKey(); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException + | NoSuchProviderException | ProviderException e) { + Log.e(TAG, "Failure in generateMacRandomizationSecret", e); + return null; + } + } + /** * Compare existing and new WifiEnterpriseConfig objects after a network update and return if * credential parameters have changed or not. diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java index 686b2098d..0badc6fbd 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java @@ -63,6 +63,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -111,6 +112,8 @@ public class WifiConfigManagerTest { private static final int TEST_FREQUENCY_1 = 2412; private static final int TEST_FREQUENCY_2 = 5180; private static final int TEST_FREQUENCY_3 = 5240; + private static final MacAddress TEST_RANDOMIZED_MAC = + MacAddress.fromString("d2:11:19:34:a5:20"); @Mock private Context mContext; @Mock private Clock mClock; @@ -228,9 +231,13 @@ public class WifiConfigManagerTest { // static mocking mSession = ExtendedMockito.mockitoSession() .mockStatic(WifiConfigStore.class, withSettings().lenient()) + .spyStatic(WifiConfigurationUtil.class) + .strictness(Strictness.LENIENT) .startMocking(); when(WifiConfigStore.createUserFiles(anyInt())).thenReturn(mock(List.class)); when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager); + when(WifiConfigurationUtil.calculatePersistentMacForConfiguration(any(), any())) + .thenReturn(TEST_RANDOMIZED_MAC); } /** @@ -239,7 +246,9 @@ public class WifiConfigManagerTest { @After public void cleanup() { validateMockitoUsage(); - mSession.finishMocking(); + if (mSession != null) { + mSession.finishMocking(); + } } /** @@ -368,6 +377,7 @@ public class WifiConfigManagerTest { */ @Test public void testAddingNetworkWithMatchingMacAddressOverridesField() { + int prevMappingSize = mWifiConfigManager.getRandomizedMacAddressMappingSize(); WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); Map randomizedMacAddressMapping = new HashMap<>(); final String randMac = "12:23:34:45:56:67"; @@ -382,6 +392,9 @@ public class WifiConfigManagerTest { List retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords(); assertEquals(randMac, retrievedNetworks.get(0).getRandomizedMacAddress().toString()); + // Verify that for networks that we already have randomizedMacAddressMapping saved + // we are still correctly writing into the WifiConfigStore. + assertEquals(prevMappingSize + 1, mWifiConfigManager.getRandomizedMacAddressMappingSize()); } /** @@ -391,6 +404,7 @@ public class WifiConfigManagerTest { */ @Test public void testRandomizedMacAddressIsPersistedOverForgetNetwork() { + int prevMappingSize = mWifiConfigManager.getRandomizedMacAddressMappingSize(); // Create and add an open network WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); verifyAddNetworkToWifiConfigManager(openNetwork); @@ -410,6 +424,8 @@ public class WifiConfigManagerTest { verifyAddNetworkToWifiConfigManager(openNetwork); retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords(); assertEquals(randMac, retrievedNetworks.get(0).getRandomizedMacAddress().toString()); + // Verify that we are no longer persisting the randomized MAC address with WifiConfigStore. + assertEquals(prevMappingSize, mWifiConfigManager.getRandomizedMacAddressMappingSize()); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java index c1640ce91..7173dae5b 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java @@ -25,6 +25,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; +import android.os.Binder; import android.os.PatternMatcher; import android.os.UserHandle; import android.util.Pair; @@ -39,6 +40,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import javax.crypto.Mac; + /** * Unit tests for {@link com.android.server.wifi.WifiConfigurationUtil}. */ @@ -961,6 +964,33 @@ public class WifiConfigurationUtilTest { existingConfig, newConfig)); } + /** + * Verifies that calculatePersistentMacForConfiguration produces persistent, locally generated + * MAC addresses that are valid for MAC randomization. + */ + @Test + public void testCalculatePersistentMacForConfiguration() { + // verify null inputs + assertNull(WifiConfigurationUtil.calculatePersistentMacForConfiguration(null, null)); + + // test multiple times since there is some randomness involved with hashing + int uid = Binder.getCallingUid(); + for (int i = 0; i < 10; i++) { + // Verify that a the MAC address calculated is valid + WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork(); + Mac hashFunction = WifiConfigurationUtil.obtainMacRandHashFunction(uid); + MacAddress macAddress = WifiConfigurationUtil.calculatePersistentMacForConfiguration( + config, hashFunction); + assertTrue(WifiConfiguration.isValidMacAddressForRandomization(macAddress)); + + // Verify that the secret used to generate MAC address is persistent + Mac hashFunction2 = WifiConfigurationUtil.obtainMacRandHashFunction(uid); + MacAddress macAddress2 = WifiConfigurationUtil.calculatePersistentMacForConfiguration( + config, hashFunction2); + assertEquals(macAddress, macAddress2); + } + } + private static class EnterpriseConfig { public String eap; public String phase2; -- cgit v1.2.3 From 84c9ba1c7c5271215d7a50ef23c5d57c0b38bbdb Mon Sep 17 00:00:00 2001 From: xshu Date: Thu, 12 Sep 2019 11:31:48 -0700 Subject: [MAC rand] Removing persistent storage Will now calculate the randomized MAC address directly for new wifi networks. For existing networks that already have saved randomized MAC address, we will continue to use the saved MAC. Bug: 140065828 Test: unit tests before and after factory reset Test: Verified that new MAC address entries are not populated into WifiConfigStore.xml Test: Manually verified calculated MAC addresses are consistent and valid. Test: Manually verified that the device secret is persisted over different builds Merged-In: I4b4e2cc6fe304d277661c4743e4fb86bb7500f16 Merged-In: Ie216fbaf33e2582ae1a0030c5046b0d4162255d9 (cherry picked from commit Ie216fbaf33e2582ae1a0030c5046b0d4162255d9) Change-Id: Ibb8f04c0fed9b5ac564ba65c305c7e1f00c43975 --- .../com/android/server/wifi/WifiConfigManager.java | 108 +++++++++++++++------ .../android/server/wifi/WifiConfigurationUtil.java | 102 +++++++++++++++++++ .../android/server/wifi/WifiConfigManagerTest.java | 18 +++- .../server/wifi/WifiConfigurationUtilTest.java | 30 ++++++ 4 files changed, 225 insertions(+), 33 deletions(-) diff --git a/service/java/com/android/server/wifi/WifiConfigManager.java b/service/java/com/android/server/wifi/WifiConfigManager.java index 0c175acd2..4ac4fb7a6 100644 --- a/service/java/com/android/server/wifi/WifiConfigManager.java +++ b/service/java/com/android/server/wifi/WifiConfigManager.java @@ -76,6 +76,8 @@ import java.util.List; import java.util.Map; import java.util.Set; +import javax.crypto.Mac; + /** * This class provides the APIs to manage configured Wi-Fi networks. * It deals with the following: @@ -228,6 +230,9 @@ public class WifiConfigManager { private static final int WIFI_PNO_FREQUENCY_CULLING_ENABLED_DEFAULT = 1; // 0 = disabled private static final int WIFI_PNO_RECENCY_SORTING_ENABLED_DEFAULT = 1; // 0 = disabled: + private static final MacAddress DEFAULT_MAC_ADDRESS = + MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); + /** * Expiration timeout for deleted ephemeral ssids. (1 day) */ @@ -263,6 +268,7 @@ public class WifiConfigManager { */ private final Context mContext; private final Clock mClock; + private final Mac mMac; private final UserManager mUserManager; private final BackupManagerProxy mBackupManagerProxy; private final TelephonyManager mTelephonyManager; @@ -443,6 +449,11 @@ public class WifiConfigManager { } catch (PackageManager.NameNotFoundException e) { Log.e(TAG, "Unable to resolve SystemUI's UID."); } + mMac = WifiConfigurationUtil.obtainMacRandHashFunction(Process.WIFI_UID); + if (mMac == null) { + Log.wtf(TAG, "Failed to obtain secret for MAC randomization." + + " All randomized MAC addresses are lost!"); + } } /** @@ -461,6 +472,59 @@ public class WifiConfigManager { return sb.toString(); } + @VisibleForTesting + protected int getRandomizedMacAddressMappingSize() { + return mRandomizedMacAddressMapping.size(); + } + + /** + * The persistent randomized MAC address is locally generated for each SSID and does not + * change until factory reset of the device. In the initial Q release the per-SSID randomized + * MAC is saved on the device, but in an update the storing of randomized MAC is removed. + * Instead, the randomized MAC is calculated directly from the SSID and a on device secret. + * For backward compatibility, this method first checks the device storage for saved + * randomized MAC. If it is not found or the saved MAC is invalid then it will calculate the + * randomized MAC directly. + * + * In the future as devices launched on Q no longer get supported, this method should get + * simplified to return the calculated MAC address directly. + * @param config the WifiConfiguration to obtain MAC address for. + * @return persistent MAC address for this WifiConfiguration + */ + private MacAddress getPersistentMacAddress(WifiConfiguration config) { + // mRandomizedMacAddressMapping had been the location to save randomized MAC addresses. + String persistentMacString = mRandomizedMacAddressMapping.get( + config.getSsidAndSecurityTypeString()); + // Use the MAC address stored in the storage if it exists and is valid. Otherwise + // use the MAC address calculated from a hash function as the persistent MAC. + if (persistentMacString != null) { + try { + return MacAddress.fromString(persistentMacString); + } catch (IllegalArgumentException e) { + Log.e(TAG, "Error creating randomized MAC address from stored value."); + mRandomizedMacAddressMapping.remove(config.getSsidAndSecurityTypeString()); + } + } + return WifiConfigurationUtil.calculatePersistentMacForConfiguration(config, mMac); + } + + /** + * Obtain the persistent MAC address by first reading from an internal database. If non exists + * then calculate the persistent MAC using HMAC-SHA256. + * Finally set the randomized MAC of the configuration to the randomized MAC obtained. + * @param config the WifiConfiguration to make the update + * @return the persistent MacAddress or null if the operation is unsuccessful + */ + private MacAddress setRandomizedMacToPersistentMac(WifiConfiguration config) { + MacAddress persistentMac = getPersistentMacAddress(config); + if (persistentMac == null || persistentMac.equals(config.getRandomizedMacAddress())) { + return persistentMac; + } + WifiConfiguration internalConfig = getInternalConfiguredNetwork(config.networkId); + internalConfig.setRandomizedMacAddress(persistentMac); + return persistentMac; + } + /** * Enable/disable verbose logging in WifiConfigManager & its helper classes. */ @@ -517,8 +581,7 @@ public class WifiConfigManager { * @param configuration WifiConfiguration to hide the MAC address */ private void maskRandomizedMacAddressInWifiConfiguration(WifiConfiguration configuration) { - MacAddress defaultMac = MacAddress.fromString(WifiInfo.DEFAULT_MAC_ADDRESS); - configuration.setRandomizedMacAddress(defaultMac); + configuration.setRandomizedMacAddress(DEFAULT_MAC_ADDRESS); } /** @@ -1044,34 +1107,11 @@ public class WifiConfigManager { packageName != null ? packageName : mContext.getPackageManager().getNameForUid(uid); newInternalConfig.creationTime = newInternalConfig.updateTime = createDebugTimeStampString(mClock.getWallClockMillis()); - updateRandomizedMacAddress(newInternalConfig); - - return newInternalConfig; - } - - /** - * Sets the randomized address for the given configuration from stored map if it exist. - * Otherwise generates a new randomized address and save to the stored map. - * @param config - */ - private void updateRandomizedMacAddress(WifiConfiguration config) { - // Update randomized MAC address according to stored map - final String key = config.getSsidAndSecurityTypeString(); - // If the key is not found in the current store, then it means this network has never been - // seen before. So add it to store. - if (!mRandomizedMacAddressMapping.containsKey(key)) { - mRandomizedMacAddressMapping.put(key, - config.getOrCreateRandomizedMacAddress().toString()); - } else { // Otherwise read from the store and set the WifiConfiguration - try { - config.setRandomizedMacAddress( - MacAddress.fromString(mRandomizedMacAddressMapping.get(key))); - } catch (IllegalArgumentException e) { - Log.e(TAG, "Error creating randomized MAC address from stored value."); - mRandomizedMacAddressMapping.put(key, - config.getOrCreateRandomizedMacAddress().toString()); - } + MacAddress randomizedMac = getPersistentMacAddress(newInternalConfig); + if (randomizedMac != null) { + newInternalConfig.setRandomizedMacAddress(randomizedMac); } + return newInternalConfig; } /** @@ -3027,12 +3067,16 @@ public class WifiConfigManager { } /** - * Generate randomized MAC addresses for configured networks and persist mapping to storage. + * Assign randomized MAC addresses for configured networks. + * This is needed to generate persistent randomized MAC address for existing networks when + * a device updates to Q+ for the first time since we are not calling addOrUpdateNetwork when + * we load configuration at boot. */ private void generateRandomizedMacAddresses() { for (WifiConfiguration config : getInternalConfiguredNetworks()) { - mRandomizedMacAddressMapping.put(config.getSsidAndSecurityTypeString(), - config.getOrCreateRandomizedMacAddress().toString()); + if (DEFAULT_MAC_ADDRESS.equals(config.getRandomizedMacAddress())) { + setRandomizedMacToPersistentMac(config); + } } } diff --git a/service/java/com/android/server/wifi/WifiConfigurationUtil.java b/service/java/com/android/server/wifi/WifiConfigurationUtil.java index a99253463..c59d4faf7 100644 --- a/service/java/com/android/server/wifi/WifiConfigurationUtil.java +++ b/service/java/com/android/server/wifi/WifiConfigurationUtil.java @@ -28,6 +28,9 @@ import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; import android.os.PatternMatcher; import android.os.UserHandle; +import android.security.keystore.AndroidKeyStoreProvider; +import android.security.keystore.KeyGenParameterSpec; +import android.security.keystore.KeyProperties; import android.text.TextUtils; import android.util.Log; import android.util.Pair; @@ -36,7 +39,17 @@ import com.android.internal.annotations.VisibleForTesting; import com.android.server.wifi.util.NativeUtil; import com.android.server.wifi.util.TelephonyUtil; +import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.Key; +import java.security.KeyStore; +import java.security.KeyStoreException; +import java.security.NoSuchAlgorithmException; +import java.security.NoSuchProviderException; +import java.security.ProviderException; +import java.security.UnrecoverableKeyException; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.BitSet; @@ -44,6 +57,10 @@ import java.util.Comparator; import java.util.List; import java.util.Objects; +import javax.crypto.KeyGenerator; +import javax.crypto.Mac; +import javax.crypto.SecretKey; + /** * WifiConfiguration utility for any {@link android.net.wifi.WifiConfiguration} related operations. * Currently contains: @@ -72,6 +89,10 @@ public class WifiConfigurationUtil { new Pair(MacAddress.BROADCAST_ADDRESS, MacAddress.BROADCAST_ADDRESS); private static final Pair MATCH_ALL_BSSID_PATTERN = new Pair(MacAddress.ALL_ZEROS_ADDRESS, MacAddress.ALL_ZEROS_ADDRESS); + private static final String MAC_RANDOMIZATION_ALIAS = "MacRandSecret"; + private static final long MAC_ADDRESS_VALID_LONG_MASK = (1L << 48) - 1; + private static final long MAC_ADDRESS_LOCALLY_ASSIGNED_MASK = 1L << 41; + private static final long MAC_ADDRESS_MULTICAST_MASK = 1L << 40; /** * Check whether a network configuration is visible to a user or any of its managed profiles. @@ -226,6 +247,87 @@ public class WifiConfigurationUtil { return newConfig.macRandomizationSetting != existingConfig.macRandomizationSetting; } + /** + * Computes the persistent randomized MAC of the given configuration using the given + * hash function. + * @param config the WifiConfiguration to compute MAC address for + * @param hashFunction the hash function that will perform the MAC address computation. + * @return The persistent randomized MAC address or null if inputs are invalid. + */ + public static MacAddress calculatePersistentMacForConfiguration(WifiConfiguration config, + Mac hashFunction) { + if (config == null || hashFunction == null) { + return null; + } + byte[] hashedBytes = hashFunction.doFinal( + config.getSsidAndSecurityTypeString().getBytes(StandardCharsets.UTF_8)); + ByteBuffer bf = ByteBuffer.wrap(hashedBytes); + long longFromSsid = bf.getLong(); + /** + * Masks the generated long so that it represents a valid randomized MAC address. + * Specifically, this sets the locally assigned bit to 1, multicast bit to 0 + */ + longFromSsid &= MAC_ADDRESS_VALID_LONG_MASK; + longFromSsid |= MAC_ADDRESS_LOCALLY_ASSIGNED_MASK; + longFromSsid &= ~MAC_ADDRESS_MULTICAST_MASK; + bf.clear(); + bf.putLong(0, longFromSsid); + + // MacAddress.fromBytes requires input of length 6, which is obtained from the + // last 6 bytes from the generated long. + MacAddress macAddress = MacAddress.fromBytes(Arrays.copyOfRange(bf.array(), 2, 8)); + return macAddress; + } + + /** + * Retrieves a Hash function that could be used to calculate the persistent randomized MAC + * for a WifiConfiguration. + * @param uid the UID of the KeyStore to get the secret of the hash function from. + */ + public static Mac obtainMacRandHashFunction(int uid) { + try { + KeyStore keyStore = AndroidKeyStoreProvider.getKeyStoreForUid(uid); + // tries to retrieve the secret, and generate a new one if it's unavailable. + Key key = keyStore.getKey(MAC_RANDOMIZATION_ALIAS, null); + if (key == null) { + key = generateAndPersistNewMacRandomizationSecret(uid); + } + if (key == null) { + Log.e(TAG, "Failed to generate secret for " + MAC_RANDOMIZATION_ALIAS); + return null; + } + Mac result = Mac.getInstance("HmacSHA256"); + result.init(key); + return result; + } catch (KeyStoreException | NoSuchAlgorithmException | InvalidKeyException + | UnrecoverableKeyException | NoSuchProviderException e) { + Log.e(TAG, "Failure in obtainMacRandHashFunction", e); + return null; + } + } + + /** + * Generates and returns a secret key to use for Mac randomization. + * Will also persist the generated secret inside KeyStore, accessible in the + * future with KeyGenerator#getKey. + */ + private static SecretKey generateAndPersistNewMacRandomizationSecret(int uid) { + try { + KeyGenerator keyGenerator = KeyGenerator.getInstance( + KeyProperties.KEY_ALGORITHM_HMAC_SHA256, "AndroidKeyStore"); + keyGenerator.init( + new KeyGenParameterSpec.Builder(MAC_RANDOMIZATION_ALIAS, + KeyProperties.PURPOSE_SIGN) + .setUid(uid) + .build()); + return keyGenerator.generateKey(); + } catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException + | NoSuchProviderException | ProviderException e) { + Log.e(TAG, "Failure in generateMacRandomizationSecret", e); + return null; + } + } + /** * Compare existing and new WifiEnterpriseConfig objects after a network update and return if * credential parameters have changed or not. diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java index 9d5ed04c7..dad88f3c0 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigManagerTest.java @@ -63,6 +63,7 @@ import org.mockito.InOrder; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -111,6 +112,8 @@ public class WifiConfigManagerTest { private static final int TEST_FREQUENCY_1 = 2412; private static final int TEST_FREQUENCY_2 = 5180; private static final int TEST_FREQUENCY_3 = 5240; + private static final MacAddress TEST_RANDOMIZED_MAC = + MacAddress.fromString("d2:11:19:34:a5:20"); @Mock private Context mContext; @Mock private Clock mClock; @@ -227,10 +230,14 @@ public class WifiConfigManagerTest { // static mocking mSession = ExtendedMockito.mockitoSession() .mockStatic(WifiConfigStore.class, withSettings().lenient()) + .spyStatic(WifiConfigurationUtil.class) + .strictness(Strictness.LENIENT) .startMocking(); when(WifiConfigStore.createUserFiles(anyInt(), any(UserManager.class))) .thenReturn(mock(List.class)); when(mTelephonyManager.createForSubscriptionId(anyInt())).thenReturn(mDataTelephonyManager); + when(WifiConfigurationUtil.calculatePersistentMacForConfiguration(any(), any())) + .thenReturn(TEST_RANDOMIZED_MAC); } /** @@ -239,7 +246,9 @@ public class WifiConfigManagerTest { @After public void cleanup() { validateMockitoUsage(); - mSession.finishMocking(); + if (mSession != null) { + mSession.finishMocking(); + } } /** @@ -368,6 +377,7 @@ public class WifiConfigManagerTest { */ @Test public void testAddingNetworkWithMatchingMacAddressOverridesField() { + int prevMappingSize = mWifiConfigManager.getRandomizedMacAddressMappingSize(); WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); Map randomizedMacAddressMapping = new HashMap<>(); final String randMac = "12:23:34:45:56:67"; @@ -382,6 +392,9 @@ public class WifiConfigManagerTest { List retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords(); assertEquals(randMac, retrievedNetworks.get(0).getRandomizedMacAddress().toString()); + // Verify that for networks that we already have randomizedMacAddressMapping saved + // we are still correctly writing into the WifiConfigStore. + assertEquals(prevMappingSize + 1, mWifiConfigManager.getRandomizedMacAddressMappingSize()); } /** @@ -391,6 +404,7 @@ public class WifiConfigManagerTest { */ @Test public void testRandomizedMacAddressIsPersistedOverForgetNetwork() { + int prevMappingSize = mWifiConfigManager.getRandomizedMacAddressMappingSize(); // Create and add an open network WifiConfiguration openNetwork = WifiConfigurationTestUtil.createOpenNetwork(); verifyAddNetworkToWifiConfigManager(openNetwork); @@ -410,6 +424,8 @@ public class WifiConfigManagerTest { verifyAddNetworkToWifiConfigManager(openNetwork); retrievedNetworks = mWifiConfigManager.getConfiguredNetworksWithPasswords(); assertEquals(randMac, retrievedNetworks.get(0).getRandomizedMacAddress().toString()); + // Verify that we are no longer persisting the randomized MAC address with WifiConfigStore. + assertEquals(prevMappingSize, mWifiConfigManager.getRandomizedMacAddressMappingSize()); } /** diff --git a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java index c1640ce91..7173dae5b 100644 --- a/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java +++ b/tests/wifitests/src/com/android/server/wifi/WifiConfigurationUtilTest.java @@ -25,6 +25,7 @@ import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiEnterpriseConfig; import android.net.wifi.WifiNetworkSpecifier; import android.net.wifi.WifiScanner; +import android.os.Binder; import android.os.PatternMatcher; import android.os.UserHandle; import android.util.Pair; @@ -39,6 +40,8 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; +import javax.crypto.Mac; + /** * Unit tests for {@link com.android.server.wifi.WifiConfigurationUtil}. */ @@ -961,6 +964,33 @@ public class WifiConfigurationUtilTest { existingConfig, newConfig)); } + /** + * Verifies that calculatePersistentMacForConfiguration produces persistent, locally generated + * MAC addresses that are valid for MAC randomization. + */ + @Test + public void testCalculatePersistentMacForConfiguration() { + // verify null inputs + assertNull(WifiConfigurationUtil.calculatePersistentMacForConfiguration(null, null)); + + // test multiple times since there is some randomness involved with hashing + int uid = Binder.getCallingUid(); + for (int i = 0; i < 10; i++) { + // Verify that a the MAC address calculated is valid + WifiConfiguration config = WifiConfigurationTestUtil.createOpenNetwork(); + Mac hashFunction = WifiConfigurationUtil.obtainMacRandHashFunction(uid); + MacAddress macAddress = WifiConfigurationUtil.calculatePersistentMacForConfiguration( + config, hashFunction); + assertTrue(WifiConfiguration.isValidMacAddressForRandomization(macAddress)); + + // Verify that the secret used to generate MAC address is persistent + Mac hashFunction2 = WifiConfigurationUtil.obtainMacRandHashFunction(uid); + MacAddress macAddress2 = WifiConfigurationUtil.calculatePersistentMacForConfiguration( + config, hashFunction2); + assertEquals(macAddress, macAddress2); + } + } + private static class EnterpriseConfig { public String eap; public String phase2; -- cgit v1.2.3 From 88a742d479748d1fa248f7814591b76c436f8648 Mon Sep 17 00:00:00 2001 From: "Nate(Qiang) Jiang" Date: Tue, 5 Nov 2019 14:15:18 -0800 Subject: [WifiRtt] add check to verify bw and preamble combination valid check the combination before send the request to the FW Bug: 142147874 Test: atest android.net.wifi Test: atest com.android.server.wifi Change-Id: I98961412cc582ca7b2f1e596017ddc8cd3fee7ab Merged-In: I98961412cc582ca7b2f1e596017ddc8cd3fee7ab (cherry picked from commit 27544a5140b9b33f9c77690d524dea21235744f3) --- .../com/android/server/wifi/rtt/RttNative.java | 15 ++++++++++++++ .../com/android/server/wifi/rtt/RttNativeTest.java | 24 ++++++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/service/java/com/android/server/wifi/rtt/RttNative.java b/service/java/com/android/server/wifi/rtt/RttNative.java index eaf947062..ffbf5bef9 100644 --- a/service/java/com/android/server/wifi/rtt/RttNative.java +++ b/service/java/com/android/server/wifi/rtt/RttNative.java @@ -311,6 +311,7 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub { config.channel.centerFreq1 = responder.centerFreq1; config.bw = halRttChannelBandwidthFromResponderChannelWidth(responder.channelWidth); config.preamble = halRttPreambleFromResponderPreamble(responder.preamble); + validateBwAndPreambleCombination(config.bw, config.preamble); if (config.peer == RttPeerType.NAN) { config.mustRequestLci = false; @@ -349,6 +350,20 @@ public class RttNative extends IWifiRttControllerEventCallback.Stub { return rttConfigs; } + private static void validateBwAndPreambleCombination(int bw, int preamble) { + if (bw <= RttBw.BW_20MHZ) { + return; + } + if (bw == RttBw.BW_40MHZ && preamble >= RttPreamble.HT) { + return; + } + if (bw >= RttBw.BW_80MHZ && preamble >= RttPreamble.VHT) { + return; + } + throw new IllegalArgumentException( + "bw and preamble combination is invalid, bw: " + bw + " preamble: " + preamble); + } + private static int halRttPeerTypeFromResponderType(int responderType) { switch (responderType) { case ResponderConfig.RESPONDER_AP: diff --git a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java index d2f22da6a..bd0ad321d 100644 --- a/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java +++ b/tests/wifitests/src/com/android/server/wifi/rtt/RttNativeTest.java @@ -18,6 +18,7 @@ package com.android.server.wifi.rtt; import static org.hamcrest.core.IsEqual.equalTo; +import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -42,6 +43,7 @@ import android.hardware.wifi.V1_0.WifiStatus; import android.hardware.wifi.V1_0.WifiStatusCode; import android.net.MacAddress; import android.net.wifi.rtt.RangingRequest; +import android.net.wifi.rtt.ResponderConfig; import androidx.test.filters.SmallTest; @@ -465,6 +467,28 @@ public class RttNativeTest { } } + /** + * Validation ranging with invalid bw and preamble combination will be ignored. + */ + @Test + public void testRangingWithInvalidParameterCombination() throws Exception { + int cmdId = 88; + RangingRequest request = new RangingRequest.Builder().build(); + ResponderConfig invalidConfig = new ResponderConfig( + MacAddress.fromString("08:09:08:07:06:88"), ResponderConfig.RESPONDER_AP, true, + ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_HT); + ResponderConfig config = new ResponderConfig(MacAddress.fromString("08:09:08:07:06:89"), + ResponderConfig.RESPONDER_AP, true, + ResponderConfig.CHANNEL_WIDTH_80MHZ, 0, 0, 0, ResponderConfig.PREAMBLE_VHT); + + // Add a ResponderConfig with invalid parameter, should be ignored. + request.mRttPeers.add(invalidConfig); + request.mRttPeers.add(config); + mDut.rangeRequest(cmdId, request, true); + verify(mockRttController).rangeRequest(eq(cmdId), mRttConfigCaptor.capture()); + assertEquals(request.mRttPeers.size() - 1, mRttConfigCaptor.getValue().size()); + } + // Utilities /** -- cgit v1.2.3 From bc8fa0c163c40afa999ac71cc78687bb845131ab Mon Sep 17 00:00:00 2001 From: Hai Shalom Date: Mon, 23 Sep 2019 14:36:13 -0700 Subject: [EAP-SIM] Add NAI realm decoration to pseudonym The framework stores the last pseudonym given by the server. However, in some cases, it does not contain @ suffix which some servers require. Check if NAI realm exist, and add @ to the pseudonym for subsequent connections if missing. Bug: 109795427 Test: atest ClientModeImplTest Test: Regression test in lab passed b/143503329 Merged-In: I74ef92e05e76290cbbf96297ed3329426e8b95a3 Change-Id: I74ef92e05e76290cbbf96297ed3329426e8b95a3 (cherry picked from commit 97447d4b970bf7e569d10dc6ca5829d99e9a4e44) --- .../com/android/server/wifi/ClientModeImpl.java | 21 +++++-- .../android/server/wifi/util/TelephonyUtil.java | 40 ++++++++++++++ .../android/server/wifi/ClientModeImplTest.java | 64 ++++++++++++++++++++-- 3 files changed, 114 insertions(+), 11 deletions(-) diff --git a/service/java/com/android/server/wifi/ClientModeImpl.java b/service/java/com/android/server/wifi/ClientModeImpl.java index 7e7f651de..119d6d0e8 100644 --- a/service/java/com/android/server/wifi/ClientModeImpl.java +++ b/service/java/com/android/server/wifi/ClientModeImpl.java @@ -4473,14 +4473,25 @@ public class ClientModeImpl extends StateMachine { config.enterpriseConfig.getEapMethod())) { String anonymousIdentity = mWifiNative.getEapAnonymousIdentity(mInterfaceName); - if (mVerboseLoggingEnabled) { - log("EAP Pseudonym: " + anonymousIdentity); - } - if (!TelephonyUtil.isAnonymousAtRealmIdentity(anonymousIdentity)) { + if (!TextUtils.isEmpty(anonymousIdentity) + && !TelephonyUtil + .isAnonymousAtRealmIdentity(anonymousIdentity)) { + String decoratedPseudonym = TelephonyUtil + .decoratePseudonymWith3GppRealm(getTelephonyManager(), + anonymousIdentity); + if (decoratedPseudonym != null) { + anonymousIdentity = decoratedPseudonym; + } + if (mVerboseLoggingEnabled) { + log("EAP Pseudonym: " + anonymousIdentity); + } // Save the pseudonym only if it is a real one config.enterpriseConfig.setAnonymousIdentity(anonymousIdentity); - mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID); + } else { + // Clear any stored pseudonyms + config.enterpriseConfig.setAnonymousIdentity(null); } + mWifiConfigManager.addOrUpdateNetwork(config, Process.WIFI_UID); } sendNetworkStateChangeBroadcast(mLastBssid); transitionTo(mObtainingIpState); diff --git a/service/java/com/android/server/wifi/util/TelephonyUtil.java b/service/java/com/android/server/wifi/util/TelephonyUtil.java index 4af40ddf2..3154df978 100644 --- a/service/java/com/android/server/wifi/util/TelephonyUtil.java +++ b/service/java/com/android/server/wifi/util/TelephonyUtil.java @@ -22,6 +22,7 @@ import android.net.wifi.WifiEnterpriseConfig; import android.telephony.ImsiEncryptionInfo; import android.telephony.SubscriptionManager; import android.telephony.TelephonyManager; +import android.text.TextUtils; import android.util.Base64; import android.util.Log; import android.util.Pair; @@ -730,4 +731,43 @@ public class TelephonyUtil { public static boolean isSimPresent(@Nonnull SubscriptionManager sm) { return sm.getActiveSubscriptionIdList().length > 0; } + + /** + * Decorates a pseudonym with the NAI realm, in case it wasn't provided by the server + * + * @param tm TelephonyManager instance + * @param pseudonym The pseudonym (temporary identity) provided by the server + * @return pseudonym@realm which is based on current MCC/MNC, {@code null} if SIM is + * not ready or absent. + */ + public static String decoratePseudonymWith3GppRealm(@NonNull TelephonyManager tm, + String pseudonym) { + if (tm == null || TextUtils.isEmpty(pseudonym)) { + return null; + } + if (pseudonym.contains("@")) { + // Pseudonym is already decorated + return pseudonym; + } + TelephonyManager defaultDataTm = tm.createForSubscriptionId( + SubscriptionManager.getDefaultDataSubscriptionId()); + if (defaultDataTm.getSimState() != TelephonyManager.SIM_STATE_READY) { + return null; + } + String mccMnc = defaultDataTm.getSimOperator(); + if (mccMnc == null || mccMnc.isEmpty()) { + return null; + } + + // Extract mcc & mnc from mccMnc + String mcc = mccMnc.substring(0, 3); + String mnc = mccMnc.substring(3); + + if (mnc.length() == 2) { + mnc = "0" + mnc; + } + + String realm = String.format(THREE_GPP_NAI_REALM_FORMAT, mnc, mcc); + return String.format("%s@%s", pseudonym, realm); + } } diff --git a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java index c3e31131d..9eb88b637 100644 --- a/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java +++ b/tests/wifitests/src/com/android/server/wifi/ClientModeImplTest.java @@ -1047,6 +1047,9 @@ public class ClientModeImplTest { when(mCarrierNetworkConfig.isCarrierEncryptionInfoAvailable()).thenReturn(true); + // Initial value should be "not set" + assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + triggerConnect(); // CMD_START_CONNECT should have set anonymousIdentity to anonymous@ @@ -1066,15 +1069,15 @@ public class ClientModeImplTest { mLooper.dispatchAll(); verify(mWifiNative).getEapAnonymousIdentity(any()); - // check that the anonymous identity remains anonymous@ for subsequent connections. - assertEquals(expectedAnonymousIdentity, - mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); - // verify that WifiConfigManager#addOrUpdateNetwork() was never called if there is no - // real pseudonym to be stored. i.e. Encrypted IMSI will be always used + + // Post connection value should remain "not set" + assertEquals("", mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + // verify that WifiConfigManager#addOrUpdateNetwork() was called to clear any previously + // stored pseudonym. i.e. to enable Encrypted IMSI for subsequent connections. // Note: This test will fail if future logic will have additional conditions that would // trigger "add or update network" operation. The test needs to be updated to account for // this change. - verify(mWifiConfigManager, never()).addOrUpdateNetwork(any(), anyInt()); + verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt()); } /** @@ -1126,6 +1129,55 @@ public class ClientModeImplTest { verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt()); } + /** + * Tests anonymous identity is set again whenever a connection is established for the carrier + * that supports encrypted IMSI and anonymous identity but real but not decorated pseudonym was + * provided for subsequent connections. + */ + @Test + public void testSetAnonymousIdentityWhenConnectionIsEstablishedWithNonDecoratedPseudonym() + throws Exception { + mConnectedNetwork = spy(WifiConfigurationTestUtil.createEapNetwork( + WifiEnterpriseConfig.Eap.SIM, WifiEnterpriseConfig.Phase2.NONE)); + when(mDataTelephonyManager.getSimOperator()).thenReturn("123456"); + when(mDataTelephonyManager.getSimState()).thenReturn(TelephonyManager.SIM_STATE_READY); + mConnectedNetwork.enterpriseConfig.setAnonymousIdentity(""); + + String realm = "wlan.mnc456.mcc123.3gppnetwork.org"; + String expectedAnonymousIdentity = "anonymous"; + String pseudonym = "83bcca9384fca"; + + when(mCarrierNetworkConfig.isCarrierEncryptionInfoAvailable()).thenReturn(true); + + triggerConnect(); + + // CMD_START_CONNECT should have set anonymousIdentity to anonymous@ + assertEquals(expectedAnonymousIdentity + "@" + realm, + mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + + when(mWifiConfigManager.getScanDetailCacheForNetwork(FRAMEWORK_NETWORK_ID)) + .thenReturn(mScanDetailCache); + when(mScanDetailCache.getScanDetail(sBSSID)).thenReturn( + getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq)); + when(mScanDetailCache.getScanResult(sBSSID)).thenReturn( + getGoogleGuestScanDetail(TEST_RSSI, sBSSID, sFreq).getScanResult()); + when(mWifiNative.getEapAnonymousIdentity(anyString())) + .thenReturn(pseudonym); + + mCmi.sendMessage(WifiMonitor.NETWORK_CONNECTION_EVENT, 0, 0, sBSSID); + mLooper.dispatchAll(); + + verify(mWifiNative).getEapAnonymousIdentity(any()); + assertEquals(pseudonym + "@" + realm, + mConnectedNetwork.enterpriseConfig.getAnonymousIdentity()); + // Verify that WifiConfigManager#addOrUpdateNetwork() was called if there we received a + // real pseudonym to be stored. i.e. Encrypted IMSI will be used once, followed by + // pseudonym usage in all subsequent connections. + // Note: This test will fail if future logic will have additional conditions that would + // trigger "add or update network" operation. The test needs to be updated to account for + // this change. + verify(mWifiConfigManager).addOrUpdateNetwork(any(), anyInt()); + } /** * Tests the Passpoint information is set in WifiInfo for Passpoint AP connection. */ -- cgit v1.2.3