Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a86e1ab3 authored by Yo Chiang's avatar Yo Chiang
Browse files

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
parent 7e0dbc53
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -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>

+15 −4
Original line number Diff line number Diff line
@@ -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) {
                    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--) {
+72 −15
Original line number Diff line number Diff line
@@ -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() {