blob: c2f3ba01e3ec66af9648301e4cbf81426718aacb [file] [log] [blame]
/*
* Copyright 2017 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.net.watchlist;
import android.os.Environment;
import android.util.AtomicFile;
import android.util.Log;
import android.util.Slog;
import android.util.Xml;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.FastXmlSerializer;
import com.android.internal.util.HexDump;
import com.android.internal.util.XmlUtils;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
import java.io.File;
import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.ArrayList;
import java.util.List;
import java.util.zip.CRC32;
/**
* Class for handling watchlist settings operations, like getting differential privacy secret key.
* Unlike WatchlistConfig, which will read configs that pushed from ConfigUpdater only, this class
* can read and write all settings for watchlist operations.
*/
class WatchlistSettings {
private static final String TAG = "WatchlistSettings";
private static final String FILE_NAME = "watchlist_settings.xml";
// Rappor requires min entropy input size = 48 bytes
private static final int SECRET_KEY_LENGTH = 48;
private final static WatchlistSettings sInstance = new WatchlistSettings();
private final AtomicFile mXmlFile;
private byte[] mPrivacySecretKey = null;
public static WatchlistSettings getInstance() {
return sInstance;
}
private WatchlistSettings() {
this(getSystemWatchlistFile());
}
static File getSystemWatchlistFile() {
return new File(Environment.getDataSystemDirectory(), FILE_NAME);
}
@VisibleForTesting
protected WatchlistSettings(File xmlFile) {
mXmlFile = new AtomicFile(xmlFile, "net-watchlist");
reloadSettings();
if (mPrivacySecretKey == null) {
// Generate a new secret key and save settings
mPrivacySecretKey = generatePrivacySecretKey();
saveSettings();
}
}
private void reloadSettings() {
if (!mXmlFile.exists()) {
// No settings config
return;
}
try (FileInputStream stream = mXmlFile.openRead()){
XmlPullParser parser = Xml.newPullParser();
parser.setInput(stream, StandardCharsets.UTF_8.name());
XmlUtils.beginDocument(parser, "network-watchlist-settings");
final int outerDepth = parser.getDepth();
while (XmlUtils.nextElementWithin(parser, outerDepth)) {
if (parser.getName().equals("secret-key")) {
mPrivacySecretKey = parseSecretKey(parser);
}
}
Slog.i(TAG, "Reload watchlist settings done");
} catch (IllegalStateException | NullPointerException | NumberFormatException |
XmlPullParserException | IOException | IndexOutOfBoundsException e) {
Slog.e(TAG, "Failed parsing xml", e);
}
}
private byte[] parseSecretKey(XmlPullParser parser)
throws IOException, XmlPullParserException {
parser.require(XmlPullParser.START_TAG, null, "secret-key");
byte[] key = HexDump.hexStringToByteArray(parser.nextText());
parser.require(XmlPullParser.END_TAG, null, "secret-key");
if (key == null || key.length != SECRET_KEY_LENGTH) {
Log.e(TAG, "Unable to parse secret key");
return null;
}
return key;
}
/**
* Get DP secret key.
* Make sure it is not exported or logged in anywhere.
*/
synchronized byte[] getPrivacySecretKey() {
final byte[] key = new byte[SECRET_KEY_LENGTH];
System.arraycopy(mPrivacySecretKey, 0, key, 0, SECRET_KEY_LENGTH);
return key;
}
private byte[] generatePrivacySecretKey() {
final byte[] key = new byte[SECRET_KEY_LENGTH];
(new SecureRandom()).nextBytes(key);
return key;
}
private void saveSettings() {
FileOutputStream stream;
try {
stream = mXmlFile.startWrite();
} catch (IOException e) {
Log.w(TAG, "Failed to write display settings: " + e);
return;
}
try {
XmlSerializer out = new FastXmlSerializer();
out.setOutput(stream, StandardCharsets.UTF_8.name());
out.startDocument(null, true);
out.startTag(null, "network-watchlist-settings");
out.startTag(null, "secret-key");
out.text(HexDump.toHexString(mPrivacySecretKey));
out.endTag(null, "secret-key");
out.endTag(null, "network-watchlist-settings");
out.endDocument();
mXmlFile.finishWrite(stream);
} catch (IOException e) {
Log.w(TAG, "Failed to write display settings, restoring backup.", e);
mXmlFile.failWrite(stream);
}
}
}