1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.wifi;
18
19import android.app.admin.IDevicePolicyManager;
20import android.content.Context;
21import android.os.Environment;
22import android.os.ServiceManager;
23import android.os.UserHandle;
24import android.security.Credentials;
25import android.security.KeyStore;
26import android.text.TextUtils;
27import android.util.Log;
28
29import com.android.server.net.DelayedDiskWrite;
30
31import java.io.DataInputStream;
32import java.io.DataOutputStream;
33import java.io.File;
34import java.io.FileInputStream;
35import java.io.IOException;
36import java.nio.charset.StandardCharsets;
37import java.util.Arrays;
38import java.util.HashSet;
39import java.util.Set;
40
41/**
42 * Manager class for affiliated Wifi certificates.
43 */
44public class WifiCertManager {
45    private static final String TAG = "WifiCertManager";
46    private static final String SEP = "\n";
47
48    private final Context mContext;
49    private final Set<String> mAffiliatedUserOnlyCerts = new HashSet<String>();
50    private final String mConfigFile;
51
52    private static final String CONFIG_FILE =
53            Environment.getDataDirectory() + "/misc/wifi/affiliatedcerts.txt";
54
55    private final DelayedDiskWrite mWriter = new DelayedDiskWrite();
56
57
58    WifiCertManager(Context context) {
59        this(context, CONFIG_FILE);
60    }
61
62    WifiCertManager(Context context, String configFile) {
63        mContext = context;
64        mConfigFile = configFile;
65        final byte[] bytes = readConfigFile();
66        if (bytes == null) {
67            // Config file does not exist or empty.
68            return;
69        }
70
71        String[] keys = new String(bytes, StandardCharsets.UTF_8).split(SEP);
72        for (String key : keys) {
73            mAffiliatedUserOnlyCerts.add(key);
74        }
75
76        // Remove keys that no longer exist in KeyStore.
77        if (mAffiliatedUserOnlyCerts.retainAll(Arrays.asList(listClientCertsForAllUsers()))) {
78            writeConfig();
79        }
80    }
81
82    /** @param  key Unprefixed cert key to hide from unaffiliated users. */
83    public void hideCertFromUnaffiliatedUsers(String key) {
84        if (mAffiliatedUserOnlyCerts.add(Credentials.USER_PRIVATE_KEY + key)) {
85            writeConfig();
86        }
87    }
88
89    /** @return Prefixed cert keys that are visible to the current user. */
90    public String[] listClientCertsForCurrentUser() {
91        HashSet<String> results = new HashSet<String>();
92
93        String[] keys = listClientCertsForAllUsers();
94        if (isAffiliatedUser()) {
95            return keys;
96        }
97
98        for (String key : keys) {
99            if (!mAffiliatedUserOnlyCerts.contains(key)) {
100                results.add(key);
101            }
102        }
103        return results.toArray(new String[results.size()]);
104    }
105
106    private void writeConfig() {
107        String[] values =
108                mAffiliatedUserOnlyCerts.toArray(new String[mAffiliatedUserOnlyCerts.size()]);
109        String value = TextUtils.join(SEP, values);
110        writeConfigFile(value.getBytes(StandardCharsets.UTF_8));
111    }
112
113    protected byte[] readConfigFile() {
114        byte[] bytes = null;
115        try {
116            final File file = new File(mConfigFile);
117            final long fileSize = file.exists() ? file.length() : 0;
118            if (fileSize == 0 || fileSize >= Integer.MAX_VALUE) {
119                // Config file is empty/corrupted/non-existing.
120                return bytes;
121            }
122
123            bytes = new byte[(int) file.length()];
124            final DataInputStream stream = new DataInputStream(new FileInputStream(file));
125            stream.readFully(bytes);
126        } catch (IOException e) {
127            Log.e(TAG, "readConfigFile: failed to read " + e, e);
128        }
129        return bytes;
130    }
131
132    protected void writeConfigFile(byte[] payload) {
133        final byte[] data = payload;
134        mWriter.write(mConfigFile, new DelayedDiskWrite.Writer() {
135            public void onWriteCalled(DataOutputStream out) throws IOException {
136                out.write(data, 0, data.length);
137            }
138        });
139    }
140
141    protected String[] listClientCertsForAllUsers() {
142        return KeyStore.getInstance().list(Credentials.USER_PRIVATE_KEY, UserHandle.myUserId());
143    }
144
145    protected boolean isAffiliatedUser() {
146        IDevicePolicyManager pm = IDevicePolicyManager.Stub.asInterface(
147                ServiceManager.getService(Context.DEVICE_POLICY_SERVICE));
148        boolean result = false;
149        try {
150            result = pm.isAffiliatedUser();
151        } catch (Exception e) {
152            Log.e(TAG, "failed to check user affiliation", e);
153        }
154        return result;
155    }
156}
157