1 /*
2  * Copyright 2017 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 
17 package com.android.server.net.watchlist;
18 
19 import android.os.Environment;
20 import android.util.AtomicFile;
21 import android.util.Log;
22 import android.util.Slog;
23 import android.util.Xml;
24 
25 import com.android.internal.annotations.VisibleForTesting;
26 import com.android.internal.util.FastXmlSerializer;
27 import com.android.internal.util.HexDump;
28 import com.android.internal.util.XmlUtils;
29 import com.android.modules.utils.TypedXmlPullParser;
30 import com.android.modules.utils.TypedXmlSerializer;
31 
32 import org.xmlpull.v1.XmlPullParser;
33 import org.xmlpull.v1.XmlPullParserException;
34 import org.xmlpull.v1.XmlSerializer;
35 
36 import java.io.File;
37 import java.io.FileDescriptor;
38 import java.io.FileInputStream;
39 import java.io.FileOutputStream;
40 import java.io.IOException;
41 import java.io.PrintWriter;
42 import java.nio.charset.StandardCharsets;
43 import java.security.MessageDigest;
44 import java.security.NoSuchAlgorithmException;
45 import java.security.SecureRandom;
46 import java.util.ArrayList;
47 import java.util.List;
48 import java.util.zip.CRC32;
49 
50 /**
51  * Class for handling watchlist settings operations, like getting differential privacy secret key.
52  * Unlike WatchlistConfig, which will read configs that pushed from ConfigUpdater only, this class
53  * can read and write all settings for watchlist operations.
54  */
55 class WatchlistSettings {
56 
57     private static final String TAG = "WatchlistSettings";
58 
59     private static final String FILE_NAME = "watchlist_settings.xml";
60     // Rappor requires min entropy input size = 48 bytes
61     private static final int SECRET_KEY_LENGTH = 48;
62 
63     private final static WatchlistSettings sInstance = new WatchlistSettings();
64     private final AtomicFile mXmlFile;
65 
66     private byte[] mPrivacySecretKey = null;
67 
getInstance()68     public static WatchlistSettings getInstance() {
69         return sInstance;
70     }
71 
WatchlistSettings()72     private WatchlistSettings() {
73         this(getSystemWatchlistFile());
74     }
75 
getSystemWatchlistFile()76     static File getSystemWatchlistFile() {
77         return new File(Environment.getDataSystemDirectory(), FILE_NAME);
78     }
79 
80     @VisibleForTesting
WatchlistSettings(File xmlFile)81     protected WatchlistSettings(File xmlFile) {
82         mXmlFile = new AtomicFile(xmlFile, "net-watchlist");
83         reloadSettings();
84         if (mPrivacySecretKey == null) {
85             // Generate a new secret key and save settings
86             mPrivacySecretKey = generatePrivacySecretKey();
87             saveSettings();
88         }
89     }
90 
reloadSettings()91     private void reloadSettings() {
92         if (!mXmlFile.exists()) {
93             // No settings config
94             return;
95         }
96         try (FileInputStream stream = mXmlFile.openRead()){
97             TypedXmlPullParser parser = Xml.resolvePullParser(stream);
98             XmlUtils.beginDocument(parser, "network-watchlist-settings");
99             final int outerDepth = parser.getDepth();
100             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
101                 if (parser.getName().equals("secret-key")) {
102                     mPrivacySecretKey = parseSecretKey(parser);
103                 }
104             }
105             Slog.i(TAG, "Reload watchlist settings done");
106         } catch (IllegalStateException | NullPointerException | NumberFormatException |
107                 XmlPullParserException | IOException | IndexOutOfBoundsException e) {
108             Slog.e(TAG, "Failed parsing xml", e);
109         }
110     }
111 
parseSecretKey(XmlPullParser parser)112     private byte[] parseSecretKey(XmlPullParser parser)
113             throws IOException, XmlPullParserException {
114         parser.require(XmlPullParser.START_TAG, null, "secret-key");
115         byte[] key = HexDump.hexStringToByteArray(parser.nextText());
116         parser.require(XmlPullParser.END_TAG, null, "secret-key");
117         if (key == null || key.length != SECRET_KEY_LENGTH) {
118             Log.e(TAG, "Unable to parse secret key");
119             return null;
120         }
121         return key;
122     }
123 
124     /**
125      * Get DP secret key.
126      * Make sure it is not exported or logged in anywhere.
127      */
getPrivacySecretKey()128     synchronized byte[] getPrivacySecretKey() {
129         final byte[] key = new byte[SECRET_KEY_LENGTH];
130         System.arraycopy(mPrivacySecretKey, 0, key, 0, SECRET_KEY_LENGTH);
131         return key;
132     }
133 
generatePrivacySecretKey()134     private byte[] generatePrivacySecretKey() {
135         final byte[] key = new byte[SECRET_KEY_LENGTH];
136         (new SecureRandom()).nextBytes(key);
137         return key;
138     }
139 
saveSettings()140     private void saveSettings() {
141         FileOutputStream stream;
142         try {
143             stream = mXmlFile.startWrite();
144         } catch (IOException e) {
145             Log.w(TAG, "Failed to write display settings: " + e);
146             return;
147         }
148         try {
149             TypedXmlSerializer out = Xml.resolveSerializer(stream);
150             out.startDocument(null, true);
151             out.startTag(null, "network-watchlist-settings");
152             out.startTag(null, "secret-key");
153             out.text(HexDump.toHexString(mPrivacySecretKey));
154             out.endTag(null, "secret-key");
155             out.endTag(null, "network-watchlist-settings");
156             out.endDocument();
157             mXmlFile.finishWrite(stream);
158         } catch (IOException e) {
159             Log.w(TAG, "Failed to write display settings, restoring backup.", e);
160             mXmlFile.failWrite(stream);
161         }
162     }
163 }
164