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