Merge "Adds support for the CertBlacklister." into jb-dev
diff --git a/services/java/com/android/server/CertBlacklister.java b/services/java/com/android/server/CertBlacklister.java
new file mode 100644
index 0000000..8b167d7
--- /dev/null
+++ b/services/java/com/android/server/CertBlacklister.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.content.Context;
+import android.content.ContentResolver;
+import android.database.ContentObserver;
+import android.os.Binder;
+import android.os.FileUtils;
+import android.provider.Settings;
+import android.util.Slog;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+
+import libcore.io.IoUtils;
+
+/**
+ * <p>CertBlacklister provides a simple mechanism for updating the platform blacklists for SSL
+ * certificate public keys and serial numbers.
+ */
+public class CertBlacklister extends Binder {
+
+    private static final String TAG = "CertBlacklister";
+
+    private static final String BLACKLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
+
+    public static final String PUBKEY_PATH = BLACKLIST_ROOT + "pubkey_blacklist.txt";
+    public static final String SERIAL_PATH = BLACKLIST_ROOT + "serial_blacklist.txt";
+
+    public static final String PUBKEY_BLACKLIST_KEY = "pubkey_blacklist";
+    public static final String SERIAL_BLACKLIST_KEY = "serial_blacklist";
+
+    private static class BlacklistObserver extends ContentObserver {
+
+        private final String mKey;
+        private final String mName;
+        private final String mPath;
+        private final File mTmpDir;
+        private final ContentResolver mContentResolver;
+
+        public BlacklistObserver(String key, String name, String path, ContentResolver cr) {
+            super(null);
+            mKey = key;
+            mName = name;
+            mPath = path;
+            mTmpDir = new File(mPath).getParentFile();
+            mContentResolver = cr;
+        }
+
+        @Override
+        public void onChange(boolean selfChange) {
+            super.onChange(selfChange);
+            writeBlacklist();
+        }
+
+        public String getValue() {
+            return Settings.Secure.getString(mContentResolver, mKey);
+        }
+
+        private void writeBlacklist() {
+            new Thread("BlacklistUpdater") {
+                public void run() {
+                    synchronized(mTmpDir) {
+                        String blacklist = getValue();
+                        if (blacklist != null) {
+                            Slog.i(TAG, "Certificate blacklist changed, updating...");
+                            FileOutputStream out = null;
+                            try {
+                                // create a temporary file
+                                File tmp = File.createTempFile("journal", "", mTmpDir);
+                                // mark it -rw-r--r--
+                                tmp.setReadable(true, false);
+                                // write to it
+                                out = new FileOutputStream(tmp);
+                                out.write(blacklist.getBytes());
+                                // sync to disk
+                                FileUtils.sync(out);
+                                // atomic rename
+                                tmp.renameTo(new File(mPath));
+                                Slog.i(TAG, "Certificate blacklist updated");
+                            } catch (IOException e) {
+                                Slog.e(TAG, "Failed to write blacklist", e);
+                            } finally {
+                                IoUtils.closeQuietly(out);
+                            }
+                        }
+                    }
+                }
+            }.start();
+        }
+    }
+
+    public CertBlacklister(Context context) {
+        registerObservers(context.getContentResolver());
+    }
+
+    private BlacklistObserver buildPubkeyObserver(ContentResolver cr) {
+        return new BlacklistObserver(PUBKEY_BLACKLIST_KEY,
+                    "pubkey",
+                    PUBKEY_PATH,
+                    cr);
+    }
+
+    private BlacklistObserver buildSerialObserver(ContentResolver cr) {
+        return new BlacklistObserver(SERIAL_BLACKLIST_KEY,
+                    "serial",
+                    SERIAL_PATH,
+                    cr);
+    }
+
+    private void registerObservers(ContentResolver cr) {
+        // set up the public key blacklist observer
+        cr.registerContentObserver(
+            Settings.Secure.getUriFor(PUBKEY_BLACKLIST_KEY),
+            true,
+            buildPubkeyObserver(cr)
+        );
+
+        // set up the serial number blacklist observer
+        cr.registerContentObserver(
+            Settings.Secure.getUriFor(SERIAL_BLACKLIST_KEY),
+            true,
+            buildSerialObserver(cr)
+        );
+    }
+}
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index d9833ab..bb103580 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -623,6 +623,13 @@
             } catch (Throwable e) {
                 reportWtf("starting CommonTimeManagementService service", e);
             }
+
+            try {
+                Slog.i(TAG, "CertBlacklister");
+                CertBlacklister blacklister = new CertBlacklister(context);
+            } catch (Throwable e) {
+                reportWtf("starting CertBlacklister", e);
+            }
             
             if (context.getResources().getBoolean(
                     com.android.internal.R.bool.config_enableDreams)) {
diff --git a/services/tests/servicestests/src/com/android/server/CertBlacklisterTest.java b/services/tests/servicestests/src/com/android/server/CertBlacklisterTest.java
new file mode 100644
index 0000000..10b9e7c
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/CertBlacklisterTest.java
@@ -0,0 +1,169 @@
+/*
+ * Copyright (C) 2012 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;
+
+import android.content.Context;
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.provider.Settings;
+import android.util.Log;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.util.HashSet;
+
+import libcore.io.IoUtils;
+
+/**
+ * Tests for {@link com.android.server.CertBlacklister}
+ */
+public class CertBlacklisterTest extends AndroidTestCase {
+
+    private static final String BLACKLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
+
+    public static final String PUBKEY_PATH = BLACKLIST_ROOT + "pubkey_blacklist.txt";
+    public static final String SERIAL_PATH = BLACKLIST_ROOT + "serial_blacklist.txt";
+
+    public static final String PUBKEY_KEY = "pubkey_blacklist";
+    public static final String SERIAL_KEY = "serial_blacklist";
+
+    private void overrideSettings(String key, String value) throws Exception {
+        Settings.Secure.putString(mContext.getContentResolver(), key, value);
+        Thread.sleep(1000);
+    }
+
+    public void testClearBlacklistPubkey() throws Exception {
+        // clear the gservices setting for a clean slate
+        overrideSettings(PUBKEY_KEY, "");
+        // read the contents of the pubkey blacklist
+        String blacklist = IoUtils.readFileAsString(PUBKEY_PATH);
+        // Verify that it's empty
+        assertEquals("", blacklist);
+    }
+
+    public void testSetBlacklistPubkey() throws Exception {
+        // build a new thing to blacklist
+        String badPubkey = "7ccabd7db47e94a5759901b6a7dfd45d1c091ccc";
+        // add the gservices override
+        overrideSettings(PUBKEY_KEY, badPubkey);
+        // check the contents again
+        String blacklist = IoUtils.readFileAsString(PUBKEY_PATH);
+        // make sure that we're equal to the string we sent out
+        assertEquals(badPubkey, blacklist);
+    }
+
+    public void testChangeBlacklistPubkey() throws Exception {
+        String badPubkey = "6ccabd7db47e94a5759901b6a7dfd45d1c091ccc";
+        overrideSettings(PUBKEY_KEY, badPubkey);
+        badPubkey = "6ccabd7db47e94a5759901b6a7dfd45d1c091cce";
+        overrideSettings(PUBKEY_KEY, badPubkey);
+        String blacklist = IoUtils.readFileAsString(PUBKEY_PATH);
+        assertEquals(badPubkey, blacklist);
+    }
+
+    public void testMultiBlacklistPubkey() throws Exception {
+        String badPubkey = "6ccabd7db47e94a5759901b6a7dfd45d1c091ccc,6ccabd7db47e94a5759901b6a7dfd45d1c091ccd";
+        overrideSettings(PUBKEY_KEY, badPubkey);
+        String blacklist = IoUtils.readFileAsString(PUBKEY_PATH);
+        assertEquals(badPubkey, blacklist);
+    }
+
+    public void testInvalidMultiBlacklistPubkey() throws Exception {
+        String badPubkey = "6ccabd7db47e94a5759901b6a7dfd45d1c091ccc,ZZZZZ,6ccabd7db47e94a5759901b6a7dfd45d1c091ccd";
+        overrideSettings(PUBKEY_KEY, badPubkey);
+        String blacklist = IoUtils.readFileAsString(PUBKEY_PATH);
+        assertEquals(badPubkey, blacklist);
+    }
+
+    public void testInvalidCharsBlacklistPubkey() throws Exception {
+        String badPubkey = "\n6ccabd7db47e94a5759901b6a7dfd45d1c091ccc,-ZZZZZ,+6ccabd7db47e94a5759901b6a7dfd45d1c091ccd";
+        overrideSettings(PUBKEY_KEY, badPubkey);
+        String blacklist = IoUtils.readFileAsString(PUBKEY_PATH);
+        assertEquals(badPubkey, blacklist);
+    }
+
+    public void testLotsOfBlacklistedPubkeys() throws Exception {
+        StringBuilder bl = new StringBuilder();
+        for (int i=0; i < 1000; i++) {
+            bl.append("6ccabd7db47e94a5759901b6a7dfd45d1c091ccc,");
+        }
+        overrideSettings(PUBKEY_KEY, bl.toString());
+        String blacklist = IoUtils.readFileAsString(PUBKEY_PATH);
+        assertEquals(bl.toString(), blacklist);
+    }
+
+    public void testClearBlacklistSerial() throws Exception {
+        // clear the gservices setting for a clean slate
+        overrideSettings(SERIAL_KEY, "");
+        // read the contents of the pubkey blacklist
+        String blacklist = IoUtils.readFileAsString(SERIAL_PATH);
+        // Verify that it's empty
+        assertEquals("", blacklist);
+    }
+
+    public void testSetBlacklistSerial() throws Exception {
+        // build a new thing to blacklist
+        String badSerial = "22e514121e61c643b1e9b06bd4b9f7d0";
+        // add the gservices override
+        overrideSettings(SERIAL_KEY, badSerial);
+        // check the contents again
+        String blacklist = IoUtils.readFileAsString(SERIAL_PATH);
+        // make sure that we're equal to the string we sent out
+        assertEquals(badSerial, blacklist);
+    }
+
+    public void testChangeBlacklistSerial() throws Exception {
+        String badSerial = "22e514121e61c643b1e9b06bd4b9f7d0";
+        overrideSettings(SERIAL_KEY, badSerial);
+        badSerial = "22e514121e61c643b1e9b06bd4b9f7d1";
+        overrideSettings(SERIAL_KEY, badSerial);
+        String blacklist = IoUtils.readFileAsString(SERIAL_PATH);
+        assertEquals(badSerial, blacklist);
+    }
+
+    public void testMultiBlacklistSerial() throws Exception {
+        String badSerial = "22e514121e61c643b1e9b06bd4b9f7d0,22e514121e61c643b1e9b06bd4b9f7d1";
+        overrideSettings(SERIAL_KEY, badSerial);
+        String blacklist = IoUtils.readFileAsString(SERIAL_PATH);
+        assertEquals(badSerial, blacklist);
+    }
+
+    public void testInvalidMultiBlacklistSerial() throws Exception {
+        String badSerial = "22e514121e61c643b1e9b06bd4b9f7d0,ZZZZ,22e514121e61c643b1e9b06bd4b9f7d1";
+        overrideSettings(SERIAL_KEY, badSerial);
+        String blacklist = IoUtils.readFileAsString(SERIAL_PATH);
+        assertEquals(badSerial, blacklist);
+    }
+
+    public void testInvalidCharsBlacklistSerial() throws Exception {
+        String badSerial = "\n22e514121e61c643b1e9b06bd4b9f7d0,-ZZZZ,+22e514121e61c643b1e9b06bd4b9f7d1";
+        overrideSettings(SERIAL_KEY, badSerial);
+        String blacklist = IoUtils.readFileAsString(SERIAL_PATH);
+        assertEquals(badSerial, blacklist);
+    }
+
+    public void testLotsOfBlacklistedSerials() throws Exception {
+        StringBuilder bl = new StringBuilder();
+        for (int i=0; i < 1000; i++) {
+            bl.append("22e514121e61c643b1e9b06bd4b9f7d0,");
+        }
+        overrideSettings(SERIAL_KEY, bl.toString());
+        String blacklist = IoUtils.readFileAsString(SERIAL_PATH);
+        assertEquals(bl.toString(), blacklist);
+    }
+}