summaryrefslogtreecommitdiff
path: root/packages/DynamicSystemInstallationService/src
diff options
context:
space:
mode:
Diffstat (limited to 'packages/DynamicSystemInstallationService/src')
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java19
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java30
-rw-r--r--packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java148
3 files changed, 195 insertions, 2 deletions
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
index 9ccb837cf613..9bae223a0a3e 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/DynamicSystemInstallationService.java
@@ -46,6 +46,7 @@ import android.app.PendingIntent;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
+import android.net.http.HttpResponseCache;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
@@ -60,6 +61,8 @@ import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
+import java.io.File;
+import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
@@ -146,10 +149,26 @@ public class DynamicSystemInstallationService extends Service
prepareNotification();
mDynSystem = (DynamicSystemManager) getSystemService(Context.DYNAMIC_SYSTEM_SERVICE);
+
+ // Install an HttpResponseCache in the application cache directory so we can cache
+ // gsi key revocation list. The http(s) protocol handler uses this cache transparently.
+ // The cache size is chosen heuristically. Since we don't have too much traffic right now,
+ // a moderate size of 1MiB should be enough.
+ try {
+ File httpCacheDir = new File(getCacheDir(), "httpCache");
+ long httpCacheSize = 1 * 1024 * 1024; // 1 MiB
+ HttpResponseCache.install(httpCacheDir, httpCacheSize);
+ } catch (IOException e) {
+ Log.d(TAG, "HttpResponseCache.install() failed: " + e);
+ }
}
@Override
public void onDestroy() {
+ HttpResponseCache cache = HttpResponseCache.getInstalled();
+ if (cache != null) {
+ cache.flush();
+ }
// Cancel the persistent notification.
mNM.cancel(NOTIFICATION_ID);
}
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
index 9aea0e713179..438c435ef0e4 100644
--- a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/InstallationAsyncTask.java
@@ -25,6 +25,8 @@ import android.os.image.DynamicSystemManager;
import android.util.Log;
import android.webkit.URLUtil;
+import org.json.JSONException;
+
import java.io.BufferedInputStream;
import java.io.File;
import java.io.IOException;
@@ -100,7 +102,9 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
private final Context mContext;
private final DynamicSystemManager mDynSystem;
private final ProgressListener mListener;
+ private final boolean mIsNetworkUrl;
private DynamicSystemManager.Session mInstallationSession;
+ private KeyRevocationList mKeyRevocationList;
private boolean mIsZip;
private boolean mIsCompleted;
@@ -123,6 +127,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
mContext = context;
mDynSystem = dynSystem;
mListener = listener;
+ mIsNetworkUrl = URLUtil.isNetworkUrl(mUrl);
}
@Override
@@ -152,9 +157,11 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
return null;
}
+ // TODO(yochiang): do post-install public key check (revocation list / boot-ramdisk)
+
mDynSystem.finishInstallation();
} catch (Exception e) {
- e.printStackTrace();
+ Log.e(TAG, e.toString(), e);
mDynSystem.remove();
return e;
} finally {
@@ -220,7 +227,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
String.format(Locale.US, "Unsupported file format: %s", mUrl));
}
- if (URLUtil.isNetworkUrl(mUrl)) {
+ if (mIsNetworkUrl) {
mStream = new URL(mUrl).openStream();
} else if (URLUtil.isFileUrl(mUrl)) {
if (mIsZip) {
@@ -234,6 +241,25 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
throw new UnsupportedUrlException(
String.format(Locale.US, "Unsupported URL: %s", mUrl));
}
+
+ // TODO(yochiang): Bypass this check if device is unlocked
+ try {
+ String listUrl = mContext.getString(R.string.key_revocation_list_url);
+ mKeyRevocationList = KeyRevocationList.fromUrl(new URL(listUrl));
+ } catch (IOException | JSONException e) {
+ Log.d(TAG, "Failed to fetch Dynamic System Key Revocation List");
+ mKeyRevocationList = new KeyRevocationList();
+ keyRevocationThrowOrWarning(e);
+ }
+ }
+
+ private void keyRevocationThrowOrWarning(Exception e) throws Exception {
+ if (mIsNetworkUrl) {
+ throw e;
+ } else {
+ // If DSU is being installed from a local file URI, then be permissive
+ Log.w(TAG, e.toString());
+ }
}
private void installUserdata() throws Exception {
diff --git a/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
new file mode 100644
index 000000000000..522bc547325b
--- /dev/null
+++ b/packages/DynamicSystemInstallationService/src/com/android/dynsystem/KeyRevocationList.java
@@ -0,0 +1,148 @@
+/*
+ * Copyright (C) 2019 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.dynsystem;
+
+import android.text.TextUtils;
+import android.util.Log;
+
+import com.android.internal.annotations.VisibleForTesting;
+
+import org.json.JSONArray;
+import org.json.JSONException;
+import org.json.JSONObject;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.HashMap;
+
+class KeyRevocationList {
+
+ private static final String TAG = "KeyRevocationList";
+
+ private static final String JSON_ENTRIES = "entries";
+ private static final String JSON_PUBLIC_KEY = "public_key";
+ private static final String JSON_STATUS = "status";
+ private static final String JSON_REASON = "reason";
+
+ private static final String STATUS_REVOKED = "REVOKED";
+
+ @VisibleForTesting
+ HashMap<String, RevocationStatus> mEntries;
+
+ static class RevocationStatus {
+ final String mStatus;
+ final String mReason;
+
+ RevocationStatus(String status, String reason) {
+ mStatus = status;
+ mReason = reason;
+ }
+ }
+
+ KeyRevocationList() {
+ mEntries = new HashMap<String, RevocationStatus>();
+ }
+
+ /**
+ * Returns the revocation status of a public key.
+ *
+ * @return a RevocationStatus for |publicKey|, null if |publicKey| doesn't exist.
+ */
+ RevocationStatus getRevocationStatusForKey(String publicKey) {
+ return mEntries.get(publicKey);
+ }
+
+ /** Test if a public key is revoked or not. */
+ boolean isRevoked(String publicKey) {
+ RevocationStatus entry = getRevocationStatusForKey(publicKey);
+ return entry != null && TextUtils.equals(entry.mStatus, STATUS_REVOKED);
+ }
+
+ @VisibleForTesting
+ void addEntry(String publicKey, String status, String reason) {
+ mEntries.put(publicKey, new RevocationStatus(status, reason));
+ }
+
+ /**
+ * Creates a KeyRevocationList from a JSON String.
+ *
+ * @param jsonString the revocation list, for example:
+ * <pre>{@code
+ * {
+ * "entries": [
+ * {
+ * "public_key": "00fa2c6637c399afa893fe83d85f3569998707d5",
+ * "status": "REVOKED",
+ * "reason": "Revocation Reason"
+ * }
+ * ]
+ * }
+ * }</pre>
+ *
+ * @throws JSONException if |jsonString| is malformed.
+ */
+ static KeyRevocationList fromJsonString(String jsonString) throws JSONException {
+ JSONObject jsonObject = new JSONObject(jsonString);
+ KeyRevocationList list = new KeyRevocationList();
+ Log.d(TAG, "Begin of revocation list");
+ if (jsonObject.has(JSON_ENTRIES)) {
+ JSONArray entries = jsonObject.getJSONArray(JSON_ENTRIES);
+ for (int i = 0; i < entries.length(); ++i) {
+ JSONObject entry = entries.getJSONObject(i);
+ String publicKey = entry.getString(JSON_PUBLIC_KEY);
+ String status = entry.getString(JSON_STATUS);
+ String reason = entry.has(JSON_REASON) ? entry.getString(JSON_REASON) : "";
+ list.addEntry(publicKey, status, reason);
+ Log.d(TAG, "Revocation entry: " + entry.toString());
+ }
+ }
+ Log.d(TAG, "End of revocation list");
+ return list;
+ }
+
+ /**
+ * Creates a KeyRevocationList from a URL.
+ *
+ * @throws IOException if |url| is inaccessible.
+ * @throws JSONException if fetched content is malformed.
+ */
+ static KeyRevocationList fromUrl(URL url) throws IOException, JSONException {
+ Log.d(TAG, "Fetch from URL: " + url.toString());
+ // Force "conditional GET"
+ // Force validate the cached result with server each time, and use the cached result
+ // only if it is validated by server, else fetch new data from server.
+ // Ref: https://developer.android.com/reference/android/net/http/HttpResponseCache#force-a-network-response
+ URLConnection connection = url.openConnection();
+ connection.setUseCaches(true);
+ connection.addRequestProperty("Cache-Control", "max-age=0");
+ try (InputStream stream = connection.getInputStream()) {
+ return fromJsonString(readFully(stream));
+ }
+ }
+
+ private static String readFully(InputStream in) throws IOException {
+ int n;
+ byte[] buffer = new byte[4096];
+ StringBuilder builder = new StringBuilder();
+ while ((n = in.read(buffer, 0, 4096)) > -1) {
+ builder.append(new String(buffer, 0, n));
+ }
+ return builder.toString();
+ }
+}