Check DSU public key with key revocation list

Throw RevocationListFetchException if failed to fetch key revocation
list.
Throw KeyRevokedException if DSU intent or image public key is revoked.
Throw PublicKeyException if getAvbPublicKey() failed.

Bug: 128892201
Test: adb shell am start-activity \
    -n com.android.dynsystem/com.android.dynsystem.VerificationActivity \
    -a android.os.image.action.START_INSTALL \
    --el KEY_USERDATA_SIZE 8192 \
    -d file:///storage/emulated/0/Download/aosp_arm64-dsu_test.zip \
    --es KEY_PUBKEY ${IMAGE_KEY}
Test: edit the code so that imageValidationThrowOrWarning() always
Test: throw and observe the logcat and device notification
Change-Id: I33733c019b305c45e7d2511c44ef1d9b446ea52e
This commit is contained in:
Yo Chiang
2020-02-06 03:44:08 +08:00
parent 7e0dbc53e3
commit a86e1ab30a
3 changed files with 89 additions and 19 deletions

View File

@@ -18,6 +18,8 @@
<string name="notification_install_inprogress">Install in progress</string>
<!-- Displayed on notification: Dynamic System installation failed [CHAR LIMIT=128] -->
<string name="notification_install_failed">Install failed</string>
<!-- Displayed on notification: Image validation failed [CHAR LIMIT=128] -->
<string name="notification_image_validation_failed">Image validation failed. Abort installation.</string>
<!-- Displayed on notification: We are running in Dynamic System [CHAR LIMIT=128] -->
<string name="notification_dynsystem_in_use">Currently running a dynamic system. Restart to use the original Android version.</string>

View File

@@ -80,6 +80,7 @@ public class DynamicSystemInstallationService extends Service
static final String KEY_ENABLE_WHEN_COMPLETED = "KEY_ENABLE_WHEN_COMPLETED";
static final String KEY_DSU_SLOT = "KEY_DSU_SLOT";
static final String DEFAULT_DSU_SLOT = "dsu";
static final String KEY_PUBKEY = "KEY_PUBKEY";
/*
* Intent actions
@@ -267,6 +268,7 @@ public class DynamicSystemInstallationService extends Service
long userdataSize = intent.getLongExtra(DynamicSystemClient.KEY_USERDATA_SIZE, 0);
mEnableWhenCompleted = intent.getBooleanExtra(KEY_ENABLE_WHEN_COMPLETED, false);
String dsuSlot = intent.getStringExtra(KEY_DSU_SLOT);
String publicKey = intent.getStringExtra(KEY_PUBKEY);
if (TextUtils.isEmpty(dsuSlot)) {
dsuSlot = DEFAULT_DSU_SLOT;
@@ -274,7 +276,7 @@ public class DynamicSystemInstallationService extends Service
// TODO: better constructor or builder
mInstallTask =
new InstallationAsyncTask(
url, dsuSlot, systemSize, userdataSize, this, mDynSystem, this);
url, dsuSlot, publicKey, systemSize, userdataSize, this, mDynSystem, this);
mInstallTask.execute();
@@ -408,6 +410,10 @@ public class DynamicSystemInstallationService extends Service
}
private Notification buildNotification(int status, int cause) {
return buildNotification(status, cause, null);
}
private Notification buildNotification(int status, int cause, Throwable detail) {
Notification.Builder builder = new Notification.Builder(this, NOTIFICATION_CHANNEL_ID)
.setSmallIcon(R.drawable.ic_system_update_googblue_24dp)
.setProgress(0, 0, false);
@@ -463,7 +469,12 @@ public class DynamicSystemInstallationService extends Service
case STATUS_NOT_STARTED:
if (cause != CAUSE_NOT_SPECIFIED && cause != CAUSE_INSTALL_CANCELLED) {
builder.setContentText(getString(R.string.notification_install_failed));
if (detail instanceof InstallationAsyncTask.ImageValidationException) {
builder.setContentText(
getString(R.string.notification_image_validation_failed));
} else {
builder.setContentText(getString(R.string.notification_install_failed));
}
} else {
// no need to notify the user if the task is not started, or cancelled.
}
@@ -525,7 +536,7 @@ public class DynamicSystemInstallationService extends Service
break;
}
Log.d(TAG, "status=" + statusString + ", cause=" + causeString);
Log.d(TAG, "status=" + statusString + ", cause=" + causeString + ", detail=" + detail);
boolean notifyOnNotificationBar = true;
@@ -538,7 +549,7 @@ public class DynamicSystemInstallationService extends Service
}
if (notifyOnNotificationBar) {
mNM.notify(NOTIFICATION_ID, buildNotification(status, cause));
mNM.notify(NOTIFICATION_ID, buildNotification(status, cause, detail));
}
for (int i = mClients.size() - 1; i >= 0; i--) {

View File

@@ -17,6 +17,7 @@
package com.android.dynsystem;
import android.content.Context;
import android.gsi.AvbPublicKey;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.MemoryFile;
@@ -51,18 +52,46 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
private static final List<String> UNSUPPORTED_PARTITIONS =
Arrays.asList("vbmeta", "boot", "userdata", "dtbo", "super_empty", "system_other");
private class UnsupportedUrlException extends RuntimeException {
private class UnsupportedUrlException extends Exception {
private UnsupportedUrlException(String message) {
super(message);
}
}
private class UnsupportedFormatException extends RuntimeException {
private class UnsupportedFormatException extends Exception {
private UnsupportedFormatException(String message) {
super(message);
}
}
static class ImageValidationException extends Exception {
ImageValidationException(String message) {
super(message);
}
ImageValidationException(Throwable cause) {
super(cause);
}
}
static class RevocationListFetchException extends ImageValidationException {
RevocationListFetchException(Throwable cause) {
super(cause);
}
}
static class KeyRevokedException extends ImageValidationException {
KeyRevokedException(String message) {
super(message);
}
}
static class PublicKeyException extends ImageValidationException {
PublicKeyException(String message) {
super(message);
}
}
/** UNSET means the installation is not completed */
static final int RESULT_UNSET = 0;
static final int RESULT_OK = 1;
@@ -97,6 +126,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
private final String mUrl;
private final String mDsuSlot;
private final String mPublicKey;
private final long mSystemSize;
private final long mUserdataSize;
private final Context mContext;
@@ -115,6 +145,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
InstallationAsyncTask(
String url,
String dsuSlot,
String publicKey,
long systemSize,
long userdataSize,
Context context,
@@ -122,6 +153,7 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
ProgressListener listener) {
mUrl = url;
mDsuSlot = dsuSlot;
mPublicKey = publicKey;
mSystemSize = systemSize;
mUserdataSize = userdataSize;
mContext = context;
@@ -157,8 +189,6 @@ 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) {
Log.e(TAG, e.toString(), e);
@@ -247,13 +277,16 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
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);
imageValidationThrowOrWarning(new RevocationListFetchException(e));
}
if (mKeyRevocationList.isRevoked(mPublicKey)) {
imageValidationThrowOrWarning(new KeyRevokedException(mPublicKey));
}
}
private void keyRevocationThrowOrWarning(Exception e) throws Exception {
private void imageValidationThrowOrWarning(ImageValidationException e)
throws ImageValidationException {
if (mIsNetworkUrl) {
throw e;
} else {
@@ -294,7 +327,8 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
}
}
private void installImages() throws IOException, InterruptedException {
private void installImages()
throws IOException, InterruptedException, ImageValidationException {
if (mStream != null) {
if (mIsZip) {
installStreamingZipUpdate();
@@ -306,12 +340,14 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
}
}
private void installStreamingGzUpdate() throws IOException, InterruptedException {
private void installStreamingGzUpdate()
throws IOException, InterruptedException, ImageValidationException {
Log.d(TAG, "To install a streaming GZ update");
installImage("system", mSystemSize, new GZIPInputStream(mStream), 1);
}
private void installStreamingZipUpdate() throws IOException, InterruptedException {
private void installStreamingZipUpdate()
throws IOException, InterruptedException, ImageValidationException {
Log.d(TAG, "To install a streaming ZIP update");
ZipInputStream zis = new ZipInputStream(mStream);
@@ -330,7 +366,8 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
}
}
private void installLocalZipUpdate() throws IOException, InterruptedException {
private void installLocalZipUpdate()
throws IOException, InterruptedException, ImageValidationException {
Log.d(TAG, "To install a local ZIP update");
Enumeration<? extends ZipEntry> entries = mZipFile.entries();
@@ -349,8 +386,9 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
}
}
private boolean installImageFromAnEntry(ZipEntry entry, InputStream is,
int numInstalledPartitions) throws IOException, InterruptedException {
private boolean installImageFromAnEntry(
ZipEntry entry, InputStream is, int numInstalledPartitions)
throws IOException, InterruptedException, ImageValidationException {
String name = entry.getName();
Log.d(TAG, "ZipEntry: " + name);
@@ -373,8 +411,9 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
return true;
}
private void installImage(String partitionName, long uncompressedSize, InputStream is,
int numInstalledPartitions) throws IOException, InterruptedException {
private void installImage(
String partitionName, long uncompressedSize, InputStream is, int numInstalledPartitions)
throws IOException, InterruptedException, ImageValidationException {
SparseInputStream sis = new SparseInputStream(new BufferedInputStream(is));
@@ -445,6 +484,24 @@ class InstallationAsyncTask extends AsyncTask<String, InstallationAsyncTask.Prog
publishProgress(progress);
}
}
AvbPublicKey avbPublicKey = new AvbPublicKey();
if (!mInstallationSession.getAvbPublicKey(avbPublicKey)) {
imageValidationThrowOrWarning(new PublicKeyException("getAvbPublicKey() failed"));
} else {
String publicKey = toHexString(avbPublicKey.sha1);
if (mKeyRevocationList.isRevoked(publicKey)) {
imageValidationThrowOrWarning(new KeyRevokedException(publicKey));
}
}
}
private static String toHexString(byte[] bytes) {
StringBuilder sb = new StringBuilder();
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
private void close() {