blob: c50f0d56c992e09c623b2c816e20062ba7a36e77 [file] [log] [blame]
Ricky Wai1a6e6672017-10-27 14:46:01 +01001/*
2 * Copyright (C) 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
17package com.android.server.net.watchlist;
18
19import android.os.Environment;
20import android.util.AtomicFile;
21import android.util.Log;
22import android.util.Xml;
23
24import com.android.internal.annotations.VisibleForTesting;
25import com.android.internal.util.FastXmlSerializer;
26import com.android.internal.util.HexDump;
27import com.android.internal.util.XmlUtils;
28
29import org.xmlpull.v1.XmlPullParser;
30import org.xmlpull.v1.XmlPullParserException;
31import org.xmlpull.v1.XmlSerializer;
32
33import java.io.File;
34import java.io.FileDescriptor;
35import java.io.FileInputStream;
36import java.io.FileNotFoundException;
37import java.io.FileOutputStream;
38import java.io.IOException;
39import java.io.PrintWriter;
40import java.nio.charset.StandardCharsets;
41import java.security.MessageDigest;
42import java.security.NoSuchAlgorithmException;
43import java.util.ArrayList;
44import java.util.List;
45import java.util.zip.CRC32;
46
47/**
48 * A util class to do watchlist settings operations, like setting watchlist, query if a domain
49 * exists in watchlist.
50 */
51class WatchlistSettings {
52 private static final String TAG = "WatchlistSettings";
53
54 // Settings xml will be stored in /data/system/network_watchlist/watchlist_settings.xml
55 static final String SYSTEM_WATCHLIST_DIR = "network_watchlist";
56
57 private static final String WATCHLIST_XML_FILE = "watchlist_settings.xml";
58
59 private static class XmlTags {
60 private static final String WATCHLIST_SETTINGS = "watchlist-settings";
61 private static final String SHA256_DOMAIN = "sha256-domain";
62 private static final String CRC32_DOMAIN = "crc32-domain";
63 private static final String SHA256_IP = "sha256-ip";
64 private static final String CRC32_IP = "crc32-ip";
65 private static final String HASH = "hash";
66 }
67
68 private static WatchlistSettings sInstance = new WatchlistSettings();
69 private final AtomicFile mXmlFile;
70 private final Object mLock = new Object();
71 private HarmfulDigests mCrc32DomainDigests = new HarmfulDigests(new ArrayList<>());
72 private HarmfulDigests mSha256DomainDigests = new HarmfulDigests(new ArrayList<>());
73 private HarmfulDigests mCrc32IpDigests = new HarmfulDigests(new ArrayList<>());
74 private HarmfulDigests mSha256IpDigests = new HarmfulDigests(new ArrayList<>());
75
76 public static synchronized WatchlistSettings getInstance() {
77 return sInstance;
78 }
79
80 private WatchlistSettings() {
81 this(getSystemWatchlistFile(WATCHLIST_XML_FILE));
82 }
83
84 @VisibleForTesting
85 protected WatchlistSettings(File xmlFile) {
86 mXmlFile = new AtomicFile(xmlFile);
87 readSettingsLocked();
88 }
89
90 static File getSystemWatchlistFile(String filename) {
91 final File dataSystemDir = Environment.getDataSystemDirectory();
92 final File systemWatchlistDir = new File(dataSystemDir, SYSTEM_WATCHLIST_DIR);
93 systemWatchlistDir.mkdirs();
94 return new File(systemWatchlistDir, filename);
95 }
96
97 private void readSettingsLocked() {
98 synchronized (mLock) {
99 FileInputStream stream;
100 try {
101 stream = mXmlFile.openRead();
102 } catch (FileNotFoundException e) {
103 Log.i(TAG, "No watchlist settings: " + mXmlFile.getBaseFile().getAbsolutePath());
104 return;
105 }
106
107 final List<byte[]> crc32DomainList = new ArrayList<>();
108 final List<byte[]> sha256DomainList = new ArrayList<>();
109 final List<byte[]> crc32IpList = new ArrayList<>();
110 final List<byte[]> sha256IpList = new ArrayList<>();
111
112 try {
113 XmlPullParser parser = Xml.newPullParser();
114 parser.setInput(stream, StandardCharsets.UTF_8.name());
115 parser.nextTag();
116 parser.require(XmlPullParser.START_TAG, null, XmlTags.WATCHLIST_SETTINGS);
117 while (parser.nextTag() == XmlPullParser.START_TAG) {
118 String tagName = parser.getName();
119 switch (tagName) {
120 case XmlTags.CRC32_DOMAIN:
121 parseHash(parser, tagName, crc32DomainList);
122 break;
123 case XmlTags.CRC32_IP:
124 parseHash(parser, tagName, crc32IpList);
125 break;
126 case XmlTags.SHA256_DOMAIN:
127 parseHash(parser, tagName, sha256DomainList);
128 break;
129 case XmlTags.SHA256_IP:
130 parseHash(parser, tagName, sha256IpList);
131 break;
132 default:
133 Log.w(TAG, "Unknown element: " + parser.getName());
134 XmlUtils.skipCurrentTag(parser);
135 }
136 }
137 parser.require(XmlPullParser.END_TAG, null, XmlTags.WATCHLIST_SETTINGS);
138 writeSettingsToMemory(crc32DomainList, sha256DomainList, crc32IpList, sha256IpList);
139 } catch (IllegalStateException | NullPointerException | NumberFormatException |
140 XmlPullParserException | IOException | IndexOutOfBoundsException e) {
141 Log.w(TAG, "Failed parsing " + e);
142 } finally {
143 try {
144 stream.close();
145 } catch (IOException e) {
146 }
147 }
148 }
149 }
150
151 private void parseHash(XmlPullParser parser, String tagName, List<byte[]> hashSet)
152 throws IOException, XmlPullParserException {
153 parser.require(XmlPullParser.START_TAG, null, tagName);
154 while (parser.nextTag() == XmlPullParser.START_TAG) {
155 parser.require(XmlPullParser.START_TAG, null, XmlTags.HASH);
156 byte[] hash = HexDump.hexStringToByteArray(parser.nextText());
157 parser.require(XmlPullParser.END_TAG, null, XmlTags.HASH);
158 hashSet.add(hash);
159 }
160 parser.require(XmlPullParser.END_TAG, null, tagName);
161 }
162
163 /**
164 * Write network watchlist settings to disk.
165 * Adb should not use it, should use writeSettingsToMemory directly instead.
166 */
167 public void writeSettingsToDisk(List<byte[]> newCrc32DomainList,
168 List<byte[]> newSha256DomainList,
169 List<byte[]> newCrc32IpList,
170 List<byte[]> newSha256IpList) {
171 synchronized (mLock) {
172 FileOutputStream stream;
173 try {
174 stream = mXmlFile.startWrite();
175 } catch (IOException e) {
176 Log.w(TAG, "Failed to write display settings: " + e);
177 return;
178 }
179
180 try {
181 XmlSerializer out = new FastXmlSerializer();
182 out.setOutput(stream, StandardCharsets.UTF_8.name());
183 out.startDocument(null, true);
184 out.startTag(null, XmlTags.WATCHLIST_SETTINGS);
185
186 writeHashSetToXml(out, XmlTags.SHA256_DOMAIN, newSha256DomainList);
187 writeHashSetToXml(out, XmlTags.SHA256_IP, newSha256IpList);
188 writeHashSetToXml(out, XmlTags.CRC32_DOMAIN, newCrc32DomainList);
189 writeHashSetToXml(out, XmlTags.CRC32_IP, newCrc32IpList);
190
191 out.endTag(null, XmlTags.WATCHLIST_SETTINGS);
192 out.endDocument();
193 mXmlFile.finishWrite(stream);
194 writeSettingsToMemory(newCrc32DomainList, newSha256DomainList, newCrc32IpList,
195 newSha256IpList);
196 } catch (IOException e) {
197 Log.w(TAG, "Failed to write display settings, restoring backup.", e);
198 mXmlFile.failWrite(stream);
199 }
200 }
201 }
202
203 /**
204 * Write network watchlist settings to memory.
205 */
206 public void writeSettingsToMemory(List<byte[]> newCrc32DomainList,
207 List<byte[]> newSha256DomainList,
208 List<byte[]> newCrc32IpList,
209 List<byte[]> newSha256IpList) {
210 synchronized (mLock) {
211 mCrc32DomainDigests = new HarmfulDigests(newCrc32DomainList);
212 mCrc32IpDigests = new HarmfulDigests(newCrc32IpList);
213 mSha256DomainDigests = new HarmfulDigests(newSha256DomainList);
214 mSha256IpDigests = new HarmfulDigests(newSha256IpList);
215 }
216 }
217
218 private static void writeHashSetToXml(XmlSerializer out, String tagName, List<byte[]> hashSet)
219 throws IOException {
220 out.startTag(null, tagName);
221 for (byte[] hash : hashSet) {
222 out.startTag(null, XmlTags.HASH);
223 out.text(HexDump.toHexString(hash));
224 out.endTag(null, XmlTags.HASH);
225 }
226 out.endTag(null, tagName);
227 }
228
229 public boolean containsDomain(String domain) {
230 // First it does a quick CRC32 check.
231 final byte[] crc32 = getCrc32(domain);
232 if (!mCrc32DomainDigests.contains(crc32)) {
233 return false;
234 }
235 // Now we do a slow SHA256 check.
236 final byte[] sha256 = getSha256(domain);
237 return mSha256DomainDigests.contains(sha256);
238 }
239
240 public boolean containsIp(String ip) {
241 // First it does a quick CRC32 check.
242 final byte[] crc32 = getCrc32(ip);
243 if (!mCrc32IpDigests.contains(crc32)) {
244 return false;
245 }
246 // Now we do a slow SHA256 check.
247 final byte[] sha256 = getSha256(ip);
248 return mSha256IpDigests.contains(sha256);
249 }
250
251
252 /** Get CRC32 of a string */
253 private byte[] getCrc32(String str) {
254 final CRC32 crc = new CRC32();
255 crc.update(str.getBytes());
256 final long tmp = crc.getValue();
257 return new byte[]{(byte)(tmp >> 24 & 255), (byte)(tmp >> 16 & 255),
258 (byte)(tmp >> 8 & 255), (byte)(tmp & 255)};
259 }
260
261 /** Get SHA256 of a string */
262 private byte[] getSha256(String str) {
263 MessageDigest messageDigest;
264 try {
265 messageDigest = MessageDigest.getInstance("SHA256");
266 } catch (NoSuchAlgorithmException e) {
267 /* can't happen */
268 return null;
269 }
270 messageDigest.update(str.getBytes());
271 return messageDigest.digest();
272 }
273
274 public void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
275 pw.println("Domain CRC32 digest list:");
276 mCrc32DomainDigests.dump(fd, pw, args);
277 pw.println("Domain SHA256 digest list:");
278 mSha256DomainDigests.dump(fd, pw, args);
279 pw.println("Ip CRC32 digest list:");
280 mCrc32IpDigests.dump(fd, pw, args);
281 pw.println("Ip SHA256 digest list:");
282 mSha256IpDigests.dump(fd, pw, args);
283 }
284}