summaryrefslogtreecommitdiffstats
path: root/service/java/com/android/server/wifi/MacAddressUtil.java
blob: effa931a57d6fbb00fd11ba6e826ebeaf0548749 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
/*
 * Copyright (C) 2019 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.wifi;

import android.net.MacAddress;
import android.net.wifi.WifiConfiguration;
import android.security.keystore.AndroidKeyStoreProvider;
import android.security.keystore.KeyGenParameterSpec;
import android.security.keystore.KeyProperties;
import android.util.Log;

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.util.Arrays;

import javax.crypto.KeyGenerator;
import javax.crypto.Mac;
import javax.crypto.SecretKey;

/**
 * Contains helper methods to support MAC randomization.
 */
public class MacAddressUtil {
    private static final String TAG = "MacAddressUtil";
    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;

    /**
     * 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 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 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 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;
        }
    }
}