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