summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/app/ActivityThread.java11
-rw-r--r--core/res/AndroidManifest.xml6
-rw-r--r--services/java/com/android/server/EventLogTags.logtags5
-rw-r--r--services/java/com/android/server/updatable/CertPinInstallReceiver.java24
-rw-r--r--services/java/com/android/server/updatable/ConfigUpdateInstallReceiver.java258
-rw-r--r--services/tests/servicestests/src/com/android/server/updatable/CertPinInstallReceiverTest.java243
6 files changed, 547 insertions, 0 deletions
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 812ac9ec73db..1cd5fa29b1b8 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -109,6 +109,7 @@ import java.util.Map;
import java.util.TimeZone;
import java.util.regex.Pattern;
+import libcore.io.EventLogger;
import libcore.io.IoUtils;
import dalvik.system.CloseGuard;
@@ -4870,6 +4871,13 @@ public final class ActivityThread {
}
}
+ private static class EventLoggingReporter implements EventLogger.Reporter {
+ @Override
+ public void report (int code, Object... list) {
+ EventLog.writeEvent(code, list);
+ }
+ }
+
public static void main(String[] args) {
SamplingProfilerIntegration.start();
@@ -4880,6 +4888,9 @@ public final class ActivityThread {
Environment.initForCurrentUser();
+ // Set the reporter for event logging in libcore
+ EventLogger.setReporter(new EventLoggingReporter());
+
Security.addProvider(new AndroidKeyStoreProvider());
Process.setArgV0("<pre-initialized>");
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 466fd24c39c5..7305d8b496bf 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2130,6 +2130,12 @@
</intent-filter>
</receiver>
+ <receiver android:name="com.android.server.updates.CertPinInstallReceiver" >
+ <intent-filter>
+ <action android:name="android.intent.action.UPDATE_PINS" />
+ </intent-filter>
+ </receiver>
+
<receiver android:name="com.android.server.MasterClearReceiver"
android:permission="android.permission.MASTER_CLEAR"
android:priority="100" >
diff --git a/services/java/com/android/server/EventLogTags.logtags b/services/java/com/android/server/EventLogTags.logtags
index 39355d537cea..840e0067ed56 100644
--- a/services/java/com/android/server/EventLogTags.logtags
+++ b/services/java/com/android/server/EventLogTags.logtags
@@ -156,3 +156,8 @@ option java_package com.android.server
51200 lockdown_vpn_connecting (egress_net|1)
51201 lockdown_vpn_connected (egress_net|1)
51202 lockdown_vpn_error (egress_net|1)
+
+# ---------------------------
+# ConfigUpdateInstallReceiver.java
+# ---------------------------
+51300 config_install_failed (dir|3)
diff --git a/services/java/com/android/server/updatable/CertPinInstallReceiver.java b/services/java/com/android/server/updatable/CertPinInstallReceiver.java
new file mode 100644
index 000000000000..c03fbc3d0aed
--- /dev/null
+++ b/services/java/com/android/server/updatable/CertPinInstallReceiver.java
@@ -0,0 +1,24 @@
+/*
+ * 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.updates;
+
+public class CertPinInstallReceiver extends ConfigUpdateInstallReceiver {
+
+ public CertPinInstallReceiver() {
+ super("/data/misc/keychain/", "pins", "metadata/", "version");
+ }
+}
diff --git a/services/java/com/android/server/updatable/ConfigUpdateInstallReceiver.java b/services/java/com/android/server/updatable/ConfigUpdateInstallReceiver.java
new file mode 100644
index 000000000000..c1f45a880b64
--- /dev/null
+++ b/services/java/com/android/server/updatable/ConfigUpdateInstallReceiver.java
@@ -0,0 +1,258 @@
+/*
+ * 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.updates;
+
+import android.content.BroadcastReceiver;
+import android.content.ContentResolver;
+import android.content.Context;
+import android.content.Intent;
+import android.provider.Settings;
+import android.os.FileUtils;
+import android.util.Base64;
+import android.util.EventLog;
+import android.util.Slog;
+
+import com.android.server.EventLogTags;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.io.FileOutputStream;
+import java.io.InputStream;
+import java.io.IOException;
+import java.security.cert.Certificate;
+import java.security.cert.CertificateException;
+import java.security.cert.CertificateFactory;
+import java.security.cert.X509Certificate;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.Signature;
+import java.security.SignatureException;
+
+import libcore.io.IoUtils;
+
+public class ConfigUpdateInstallReceiver extends BroadcastReceiver {
+
+ private static final String TAG = "ConfigUpdateInstallReceiver";
+
+ private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH";
+ private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH";
+ private static final String EXTRA_SIGNATURE = "SIGNATURE";
+ private static final String EXTRA_VERSION_NUMBER = "VERSION";
+
+ private static final String UPDATE_CERTIFICATE_KEY = "config_update_certificate";
+
+ private final File updateDir;
+ private final File updateContent;
+ private final File updateVersion;
+
+ public ConfigUpdateInstallReceiver(String updateDir, String updateContentPath,
+ String updateMetadataPath, String updateVersionPath) {
+ this.updateDir = new File(updateDir);
+ this.updateContent = new File(updateDir, updateContentPath);
+ File updateMetadataDir = new File(updateDir, updateMetadataPath);
+ this.updateVersion = new File(updateMetadataDir, updateVersionPath);
+ }
+
+ @Override
+ public void onReceive(final Context context, final Intent intent) {
+ new Thread() {
+ @Override
+ public void run() {
+ try {
+ // get the certificate from Settings.Secure
+ X509Certificate cert = getCert(context.getContentResolver());
+ // get the content path from the extras
+ String altContent = getAltContent(intent);
+ // get the version from the extras
+ int altVersion = getVersionFromIntent(intent);
+ // get the previous value from the extras
+ String altRequiredHash = getRequiredHashFromIntent(intent);
+ // get the signature from the extras
+ String altSig = getSignatureFromIntent(intent);
+ // get the version currently being used
+ int currentVersion = getCurrentVersion();
+ // get the hash of the currently used value
+ String currentHash = getCurrentHash(getCurrentContent());
+ if (!verifyVersion(currentVersion, altVersion)) {
+ Slog.e(TAG, "New version is not greater than current version, aborting!");
+ } else if (!verifyPreviousHash(currentHash, altRequiredHash)) {
+ Slog.e(TAG, "Current hash did not match required value, aborting!");
+ } else if (!verifySignature(altContent, altVersion, altRequiredHash, altSig,
+ cert)) {
+ Slog.e(TAG, "Signature did not verify, aborting!");
+ } else {
+ // install the new content
+ Slog.i(TAG, "Found new update, installing...");
+ install(altContent, altVersion);
+ Slog.i(TAG, "Installation successful");
+ }
+ } catch (Exception e) {
+ Slog.e(TAG, "Could not update content!", e);
+ EventLog.writeEvent(EventLogTags.CONFIG_INSTALL_FAILED,
+ updateDir.toString());
+ }
+ }
+ }.start();
+ }
+
+ private X509Certificate getCert(ContentResolver cr) {
+ // get the cert from settings
+ String cert = Settings.Secure.getString(cr, UPDATE_CERTIFICATE_KEY);
+ // convert it into a real certificate
+ try {
+ byte[] derCert = Base64.decode(cert.getBytes(), Base64.DEFAULT);
+ InputStream istream = new ByteArrayInputStream(derCert);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cf.generateCertificate(istream);
+ } catch (CertificateException e) {
+ throw new IllegalStateException("Got malformed certificate from settings, ignoring", e);
+ }
+ }
+
+ private String getContentFromIntent(Intent i) {
+ String extraValue = i.getStringExtra(EXTRA_CONTENT_PATH);
+ if (extraValue == null) {
+ throw new IllegalStateException("Missing required content path, ignoring.");
+ }
+ return extraValue;
+ }
+
+ private int getVersionFromIntent(Intent i) throws NumberFormatException {
+ String extraValue = i.getStringExtra(EXTRA_VERSION_NUMBER);
+ if (extraValue == null) {
+ throw new IllegalStateException("Missing required version number, ignoring.");
+ }
+ return Integer.parseInt(extraValue.trim());
+ }
+
+ private String getRequiredHashFromIntent(Intent i) {
+ String extraValue = i.getStringExtra(EXTRA_REQUIRED_HASH);
+ if (extraValue == null) {
+ throw new IllegalStateException("Missing required previous hash, ignoring.");
+ }
+ return extraValue.trim();
+ }
+
+ private String getSignatureFromIntent(Intent i) {
+ String extraValue = i.getStringExtra(EXTRA_SIGNATURE);
+ if (extraValue == null) {
+ throw new IllegalStateException("Missing required signature, ignoring.");
+ }
+ return extraValue.trim();
+ }
+
+ private int getCurrentVersion() throws NumberFormatException {
+ try {
+ String strVersion = IoUtils.readFileAsString(updateVersion.getCanonicalPath()).trim();
+ return Integer.parseInt(strVersion);
+ } catch (IOException e) {
+ Slog.i(TAG, "Couldn't find current metadata, assuming first update", e);
+ return 0;
+ }
+ }
+
+ private String getAltContent(Intent i) throws IOException {
+ String contents = IoUtils.readFileAsString(getContentFromIntent(i));
+ return contents.trim();
+ }
+
+ private String getCurrentContent() {
+ try {
+ return IoUtils.readFileAsString(updateContent.getCanonicalPath()).trim();
+ } catch (IOException e) {
+ Slog.i(TAG, "Failed to read current content, assuming first update!", e);
+ return null;
+ }
+ }
+
+ private static String getCurrentHash(String content) {
+ if (content == null) {
+ return "0";
+ }
+ try {
+ MessageDigest dgst = MessageDigest.getInstance("SHA512");
+ byte[] encoded = content.getBytes();
+ byte[] fingerprint = dgst.digest(encoded);
+ return IntegralToString.bytesToHexString(fingerprint, false);
+ } catch (NoSuchAlgorithmException e) {
+ throw new AssertionError(e);
+ }
+ }
+
+ private boolean verifyVersion(int current, int alternative) {
+ return (current < alternative);
+ }
+
+ private boolean verifyPreviousHash(String current, String required) {
+ // this is an optional value- if the required field is NONE then we ignore it
+ if (required.equals("NONE")) {
+ return true;
+ }
+ // otherwise, verify that we match correctly
+ return current.equals(required);
+ }
+
+ private boolean verifySignature(String content, int version, String requiredPrevious,
+ String signature, X509Certificate cert) throws Exception {
+ Signature signer = Signature.getInstance("SHA512withRSA");
+ signer.initVerify(cert);
+ signer.update(content.getBytes());
+ signer.update(Long.toString(version).getBytes());
+ signer.update(requiredPrevious.getBytes());
+ return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
+ }
+
+ private void writeUpdate(File dir, File file, String content) {
+ FileOutputStream out = null;
+ File tmp = null;
+ try {
+ // create the temporary file
+ tmp = File.createTempFile("journal", "", dir);
+ // create the parents for the destination file
+ File parent = file.getParentFile();
+ parent.mkdirs();
+ // check that they were created correctly
+ if (!parent.exists()) {
+ throw new IOException("Failed to create directory " + parent.getCanonicalPath());
+ }
+ // mark tmp -rw-r--r--
+ tmp.setReadable(true, false);
+ // write to it
+ out = new FileOutputStream(tmp);
+ out.write(content.getBytes());
+ // sync to disk
+ out.getFD().sync();
+ // atomic rename
+ if (!tmp.renameTo(file)) {
+ throw new IOException("Failed to atomically rename " + file.getCanonicalPath());
+ }
+ } catch (IOException e) {
+ Slog.e(TAG, "Failed to write update", e);
+ } finally {
+ if (tmp != null) {
+ tmp.delete();
+ }
+ IoUtils.closeQuietly(out);
+ }
+ }
+
+ private void install(String content, int version) {
+ writeUpdate(updateDir, updateContent, content);
+ writeUpdate(updateDir, updateVersion, Long.toString(version));
+ }
+}
diff --git a/services/tests/servicestests/src/com/android/server/updatable/CertPinInstallReceiverTest.java b/services/tests/servicestests/src/com/android/server/updatable/CertPinInstallReceiverTest.java
new file mode 100644
index 000000000000..b6742a104c30
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/updatable/CertPinInstallReceiverTest.java
@@ -0,0 +1,243 @@
+/*
+ * 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.updates;
+
+import android.content.Context;
+import android.content.Intent;
+import android.test.AndroidTestCase;
+import android.provider.Settings;
+import android.util.Base64;
+import android.util.Log;
+
+import java.io.ByteArrayInputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.FileWriter;
+import java.io.IOException;
+import java.io.InputStream;
+import java.security.cert.CertificateFactory;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.Signature;
+import java.security.spec.PKCS8EncodedKeySpec;
+import java.security.KeyFactory;
+import java.util.HashSet;
+import java.io.*;
+import libcore.io.IoUtils;
+
+/**
+ * Tests for {@link com.android.server.CertPinInstallReceiver}
+ */
+public class CertPinInstallReceiverTest extends AndroidTestCase {
+
+ private static final String TAG = "CertPinInstallReceiverTest";
+
+ private static final String PINLIST_ROOT = System.getenv("ANDROID_DATA") + "/misc/keychain/";
+
+ public static final String PINLIST_CONTENT_PATH = PINLIST_ROOT + "pins";
+ public static final String PINLIST_METADATA_PATH = PINLIST_CONTENT_PATH + "metadata";
+
+ public static final String PINLIST_CONTENT_URL_KEY = "pinlist_content_url";
+ public static final String PINLIST_METADATA_URL_KEY = "pinlist_metadata_url";
+ public static final String PINLIST_CERTIFICATE_KEY = "config_update_certificate";
+ public static final String PINLIST_VERSION_KEY = "pinlist_version";
+
+ private static final String EXTRA_CONTENT_PATH = "CONTENT_PATH";
+ private static final String EXTRA_REQUIRED_HASH = "REQUIRED_HASH";
+ private static final String EXTRA_SIGNATURE = "SIGNATURE";
+ private static final String EXTRA_VERSION_NUMBER = "VERSION";
+
+ public static final String TEST_CERT = "" +
+ "MIIDsjCCAxugAwIBAgIJAPLf2gS0zYGUMA0GCSqGSIb3DQEBBQUAMIGYMQswCQYDVQQGEwJVUzET" +
+ "MBEGA1UECBMKQ2FsaWZvcm5pYTEWMBQGA1UEBxMNTW91bnRhaW4gVmlldzEPMA0GA1UEChMGR29v" +
+ "Z2xlMRAwDgYDVQQLEwd0ZXN0aW5nMRYwFAYDVQQDEw1HZXJlbXkgQ29uZHJhMSEwHwYJKoZIhvcN" +
+ "AQkBFhJnY29uZHJhQGdvb2dsZS5jb20wHhcNMTIwNzE0MTc1MjIxWhcNMTIwODEzMTc1MjIxWjCB" +
+ "mDELMAkGA1UEBhMCVVMxEzARBgNVBAgTCkNhbGlmb3JuaWExFjAUBgNVBAcTDU1vdW50YWluIFZp" +
+ "ZXcxDzANBgNVBAoTBkdvb2dsZTEQMA4GA1UECxMHdGVzdGluZzEWMBQGA1UEAxMNR2VyZW15IENv" +
+ "bmRyYTEhMB8GCSqGSIb3DQEJARYSZ2NvbmRyYUBnb29nbGUuY29tMIGfMA0GCSqGSIb3DQEBAQUA" +
+ "A4GNADCBiQKBgQCjGGHATBYlmas+0sEECkno8LZ1KPglb/mfe6VpCT3GhSr+7br7NG/ZwGZnEhLq" +
+ "E7YIH4fxltHmQC3Tz+jM1YN+kMaQgRRjo/LBCJdOKaMwUbkVynAH6OYsKevjrOPk8lfM5SFQzJMG" +
+ "sA9+Tfopr5xg0BwZ1vA/+E3mE7Tr3M2UvwIDAQABo4IBADCB/TAdBgNVHQ4EFgQUhzkS9E6G+x8W" +
+ "L4EsmRjDxu28tHUwgc0GA1UdIwSBxTCBwoAUhzkS9E6G+x8WL4EsmRjDxu28tHWhgZ6kgZswgZgx" +
+ "CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpDYWxpZm9ybmlhMRYwFAYDVQQHEw1Nb3VudGFpbiBWaWV3" +
+ "MQ8wDQYDVQQKEwZHb29nbGUxEDAOBgNVBAsTB3Rlc3RpbmcxFjAUBgNVBAMTDUdlcmVteSBDb25k" +
+ "cmExITAfBgkqhkiG9w0BCQEWEmdjb25kcmFAZ29vZ2xlLmNvbYIJAPLf2gS0zYGUMAwGA1UdEwQF" +
+ "MAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAYiugFDmbDOQ2U/+mqNt7o8ftlEo9SJrns6O8uTtK6AvR" +
+ "orDrR1AXTXkuxwLSbmVfedMGOZy7Awh7iZa8hw5x9XmUudfNxvmrKVEwGQY2DZ9PXbrnta/dwbhK" +
+ "mWfoepESVbo7CKIhJp8gRW0h1Z55ETXD57aGJRvQS4pxkP8ANhM=";
+
+
+ public static final String TEST_KEY = "" +
+ "MIICdgIBADANBgkqhkiG9w0BAQEFAASCAmAwggJcAgEAAoGBAKMYYcBMFiWZqz7SwQQKSejwtnUo" +
+ "+CVv+Z97pWkJPcaFKv7tuvs0b9nAZmcSEuoTtggfh/GW0eZALdPP6MzVg36QxpCBFGOj8sEIl04p" +
+ "ozBRuRXKcAfo5iwp6+Os4+TyV8zlIVDMkwawD35N+imvnGDQHBnW8D/4TeYTtOvczZS/AgMBAAEC" +
+ "gYBxwFalNSwZK3WJipq+g6KLCiBn1JxGGDQlLKrweFaSuFyFky9fd3IvkIabirqQchD612sMb+GT" +
+ "0t1jptW6z4w2w6++IW0A3apDOCwoD+uvDBXrbFqI0VbyAWUNqHVdaFFIRk2IHGEE6463mGRdmILX" +
+ "IlCd/85RTHReg4rl/GFqWQJBANgLAIR4pWbl5Gm+DtY18wp6Q3pJAAMkmP/lISCBIidu1zcqYIKt" +
+ "PoDW4Knq9xnhxPbXrXKv4YzZWHBK8GkKhQ0CQQDBQnXufQcMew+PwiS0oJvS+eQ6YJwynuqG2ejg" +
+ "WE+T7489jKtscRATpUXpZUYmDLGg9bLt7L62hFvFSj2LO2X7AkBcdrD9AWnBFWlh/G77LVHczSEu" +
+ "KCoyLiqxcs5vy/TjLaQ8vw1ZQG580/qJnr+tOxyCjSJ18GK3VppsTRaBznfNAkB3nuCKNp9HTWCL" +
+ "dfrsRsFMrFpk++mSt6SoxXaMbn0LL2u1CD4PCEiQMGt+lK3/3TmRTKNs+23sYS7Ahjxj0udDAkEA" +
+ "p57Nj65WNaWeYiOfTwKXkLj8l29H5NbaGWxPT0XkWr4PvBOFZVH/wj0/qc3CMVGnv11+DyO+QUCN" +
+ "SqBB5aRe8g==";
+
+ private void overrideSettings(String key, String value) throws Exception {
+ assertTrue(Settings.Secure.putString(mContext.getContentResolver(), key, value));
+ Thread.sleep(1000);
+ }
+
+ private void overrideCert(String value) throws Exception {
+ overrideSettings(PINLIST_CERTIFICATE_KEY, value);
+ }
+
+ private String readPins() throws Exception {
+ return IoUtils.readFileAsString(PINLIST_CONTENT_PATH);
+ }
+
+ private String readCurrentVersion() throws Exception {
+ return IoUtils.readFileAsString("/data/misc/keychain/metadata/version");
+ }
+
+ private String getNextVersion() throws Exception {
+ int currentVersion = Integer.parseInt(readCurrentVersion());
+ return Integer.toString(currentVersion + 1);
+ }
+
+ private static String getCurrentHash(String content) throws Exception {
+ if (content == null) {
+ return "0";
+ }
+ MessageDigest dgst = MessageDigest.getInstance("SHA512");
+ byte[] encoded = content.getBytes();
+ byte[] fingerprint = dgst.digest(encoded);
+ return IntegralToString.bytesToHexString(fingerprint, false);
+ }
+
+ private static String getHashOfCurrentContent() throws Exception {
+ String content = IoUtils.readFileAsString("/data/misc/keychain/pins");
+ return getCurrentHash(content);
+ }
+
+ private PrivateKey createKey() throws Exception {
+ byte[] derKey = Base64.decode(TEST_KEY.getBytes(), Base64.DEFAULT);
+ PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(derKey);
+ KeyFactory keyFactory = KeyFactory.getInstance("RSA");
+ return (PrivateKey) keyFactory.generatePrivate(keySpec);
+ }
+
+ private X509Certificate createCertificate() throws Exception {
+ byte[] derCert = Base64.decode(TEST_CERT.getBytes(), Base64.DEFAULT);
+ InputStream istream = new ByteArrayInputStream(derCert);
+ CertificateFactory cf = CertificateFactory.getInstance("X.509");
+ return (X509Certificate) cf.generateCertificate(istream);
+ }
+
+ private String makeTemporaryContentFile(String content) throws Exception {
+ FileOutputStream fw = mContext.openFileOutput("content.txt", mContext.MODE_WORLD_READABLE);
+ fw.write(content.getBytes(), 0, content.length());
+ fw.close();
+ return mContext.getFilesDir() + "/content.txt";
+ }
+
+ private String createSignature(String content, String version, String requiredHash)
+ throws Exception {
+ Signature signer = Signature.getInstance("SHA512withRSA");
+ signer.initSign(createKey());
+ signer.update(content.trim().getBytes());
+ signer.update(version.trim().getBytes());
+ signer.update(requiredHash.getBytes());
+ String sig = new String(Base64.encode(signer.sign(), Base64.DEFAULT));
+ assertEquals(true,
+ verifySignature(content, version, requiredHash, sig, createCertificate()));
+ return sig;
+ }
+
+ public boolean verifySignature(String content, String version, String requiredPrevious,
+ String signature, X509Certificate cert) throws Exception {
+ Signature signer = Signature.getInstance("SHA512withRSA");
+ signer.initVerify(cert);
+ signer.update(content.trim().getBytes());
+ signer.update(version.trim().getBytes());
+ signer.update(requiredPrevious.trim().getBytes());
+ return signer.verify(Base64.decode(signature.getBytes(), Base64.DEFAULT));
+ }
+
+ private void sendIntent(String contentPath, String version, String required, String sig) {
+ Intent i = new Intent();
+ i.setAction("android.intent.action.UPDATE_PINS");
+ i.putExtra(EXTRA_CONTENT_PATH, contentPath);
+ i.putExtra(EXTRA_VERSION_NUMBER, version);
+ i.putExtra(EXTRA_REQUIRED_HASH, required);
+ i.putExtra(EXTRA_SIGNATURE, sig);
+ mContext.sendBroadcast(i);
+ }
+
+ private String runTest(String cert, String content, String version, String required, String sig)
+ throws Exception {
+ Log.e(TAG, "started test");
+ overrideCert(cert);
+ String contentPath = makeTemporaryContentFile(content);
+ sendIntent(contentPath, version, required, sig);
+ Thread.sleep(1000);
+ return readPins();
+ }
+
+ private String runTestWithoutSig(String cert, String content, String version, String required)
+ throws Exception {
+ String sig = createSignature(content, version, required);
+ return runTest(cert, content, version, required, sig);
+ }
+
+ public void testOverwritePinlist() throws Exception {
+ Log.e(TAG, "started testOverwritePinList");
+ assertEquals("abcde", runTestWithoutSig(TEST_CERT, "abcde", getNextVersion(), getHashOfCurrentContent()));
+ Log.e(TAG, "started testOverwritePinList");
+ }
+
+ public void testBadSignatureFails() throws Exception {
+ Log.e(TAG, "started testOverwritePinList");
+ String text = "blahblah";
+ runTestWithoutSig(TEST_CERT, text, getNextVersion(), getHashOfCurrentContent());
+ assertEquals(text, runTest(TEST_CERT, "bcdef", getNextVersion(), getCurrentHash(text), ""));
+ Log.e(TAG, "started testOverwritePinList");
+ }
+
+ public void testBadRequiredHashFails() throws Exception {
+ runTestWithoutSig(TEST_CERT, "blahblahblah", getNextVersion(), getHashOfCurrentContent());
+ assertEquals("blahblahblah", runTestWithoutSig(TEST_CERT, "cdefg", getNextVersion(), "0"));
+ Log.e(TAG, "started testOverwritePinList");
+ }
+
+ public void testBadVersionFails() throws Exception {
+ String text = "blahblahblahblah";
+ String version = getNextVersion();
+ runTestWithoutSig(TEST_CERT, text, version, getHashOfCurrentContent());
+ assertEquals(text, runTestWithoutSig(TEST_CERT, "defgh", version, getCurrentHash(text)));
+ Log.e(TAG, "started testOverwritePinList");
+ }
+
+ public void testOverrideRequiredHash() throws Exception {
+ runTestWithoutSig(TEST_CERT, "blahblahblah", getNextVersion(), getHashOfCurrentContent());
+ assertEquals("blahblahblah", runTestWithoutSig(TEST_CERT, "cdefg", "NONE", "0"));
+ Log.e(TAG, "started testOverwritePinList");
+ }
+
+}