Create idsig file automatically

Before a VM is started, the idsig file is created (or updated) by the
virtualization service. This is needed because the idsig file is usually
not available, especially when the APK is downloaded from the store.

Note that the generated idsig file is not a signed one. Therefore, the
APK is first verified using the APK signature scheme V3 (or V2) over a
dm-verity device backed by the APK and the merkle tree (and root hash)
from the idsig file. Only if the verification is successful, the root
hash stored to the instance.img and then used for the subsequent boots
of the VM.

Bug: 193504400
Test: atest MicrodroidHostTestCases
Test: run MicrodroidDemoApp without having the idsig file in
/data/local/tmp/virt.

Change-Id: I9fad05ca9562ae0666431102a8147d0f76f04e6a
diff --git a/demo/README.md b/demo/README.md
index 113a14e..7ae2e0e 100644
--- a/demo/README.md
+++ b/demo/README.md
@@ -10,8 +10,6 @@
 
 ```
 adb install out/dist/MicrodroidDemoApp.apk
-adb shell mkdir /data/local/tmp/virt
-adb push out/dist/MicrodroidDemoApp.apk.idsig /data/local/tmp/virt/
 ```
 
 ## Running
diff --git a/demo/java/com/android/microdroid/demo/MainActivity.java b/demo/java/com/android/microdroid/demo/MainActivity.java
index b6c7714..9374f5d 100644
--- a/demo/java/com/android/microdroid/demo/MainActivity.java
+++ b/demo/java/com/android/microdroid/demo/MainActivity.java
@@ -130,7 +130,6 @@
             try {
                 VirtualMachineConfig.Builder builder =
                         new VirtualMachineConfig.Builder(getApplication(), "assets/vm_config.json")
-                                .idsigPath("/data/local/tmp/virt/MicrodroidDemoApp.apk.idsig")
                                 .debugMode(debug);
                 VirtualMachineConfig config = builder.build();
                 VirtualMachineManager vmm = VirtualMachineManager.getInstance(getApplication());
diff --git a/idsig/Android.bp b/idsig/Android.bp
index 94aaca0..06203cd 100644
--- a/idsig/Android.bp
+++ b/idsig/Android.bp
@@ -25,6 +25,7 @@
 rust_library {
     name: "libidsig",
     defaults: ["libidsig.defaults"],
+    apex_available: ["com.android.virt"],
 }
 
 rust_test {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachine.java b/javalib/src/android/system/virtualmachine/VirtualMachine.java
index c46bb2b..3152c3b 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachine.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachine.java
@@ -16,6 +16,7 @@
 
 package android.system.virtualmachine;
 
+import static android.os.ParcelFileDescriptor.MODE_READ_ONLY;
 import static android.os.ParcelFileDescriptor.MODE_READ_WRITE;
 
 import android.annotation.NonNull;
@@ -56,6 +57,9 @@
     /** Name of the instance image file for a VM. (Not implemented) */
     private static final String INSTANCE_IMAGE_FILE = "instance.img";
 
+    /** Name of the idsig file for a VM */
+    private static final String IDSIG_FILE = "idsig";
+
     /** Name of the virtualization service. */
     private static final String SERVICE_NAME = "android.system.virtualizationservice";
 
@@ -86,6 +90,9 @@
     /** Path to the instance image file for this VM. */
     private final @NonNull File mInstanceFilePath;
 
+    /** Path to the idsig file for this VM. */
+    private final @NonNull File mIdsigFilePath;
+
     /** Size of the instance image. 10 MB. */
     private static final long INSTANCE_FILE_SIZE = 10 * 1024 * 1024;
 
@@ -111,6 +118,7 @@
         final File thisVmDir = new File(vmRoot, mName);
         mConfigFilePath = new File(thisVmDir, CONFIG_FILE);
         mInstanceFilePath = new File(thisVmDir, INSTANCE_IMAGE_FILE);
+        mIdsigFilePath = new File(thisVmDir, IDSIG_FILE);
     }
 
     /**
@@ -248,6 +256,14 @@
         if (getStatus() != Status.STOPPED) {
             throw new VirtualMachineException(this + " is not in stopped state");
         }
+
+        try {
+            mIdsigFilePath.createNewFile();
+        } catch (IOException e) {
+            // If the file already exists, exception is not thrown.
+            throw new VirtualMachineException("failed to create idsig file", e);
+        }
+
         IVirtualizationService service =
                 IVirtualizationService.Stub.asInterface(
                         ServiceManager.waitForService(SERVICE_NAME));
@@ -260,11 +276,19 @@
             }
 
             VirtualMachineAppConfig appConfig = getConfig().toParcel();
+
+            // Fill the idsig file by hashing the apk
+            service.createOrUpdateIdsigFile(
+                    appConfig.apk, ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_WRITE));
+
+            // Re-open idsig file in read-only mode
+            appConfig.idsig = ParcelFileDescriptor.open(mIdsigFilePath, MODE_READ_ONLY);
             appConfig.instanceImage = ParcelFileDescriptor.open(mInstanceFilePath, MODE_READ_WRITE);
 
             android.system.virtualizationservice.VirtualMachineConfig vmConfigParcel =
                     android.system.virtualizationservice.VirtualMachineConfig.appConfig(appConfig);
 
+
             mVirtualMachine = service.startVm(vmConfigParcel, mConsoleWriter);
             mVirtualMachine.registerCallback(
                     new IVirtualMachineCallback.Stub() {
diff --git a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
index 7a10a96..2550087 100644
--- a/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
+++ b/javalib/src/android/system/virtualmachine/VirtualMachineConfig.java
@@ -48,15 +48,13 @@
     private static final String KEY_VERSION = "version";
     private static final String KEY_CERTS = "certs";
     private static final String KEY_APKPATH = "apkPath";
-    private static final String KEY_IDSIGPATH = "idsigPath";
     private static final String KEY_PAYLOADCONFIGPATH = "payloadConfigPath";
     private static final String KEY_DEBUGMODE = "debugMode";
     private static final String KEY_MEMORY_MIB = "memoryMib";
 
-    // Paths to the APK and its idsig file of this application.
+    // Paths to the APK file of this application.
     private final @NonNull String mApkPath;
     private final @NonNull Signature[] mCerts;
-    private final @NonNull String mIdsigPath;
     private final boolean mDebugMode;
     /**
      * The amount of RAM to give the VM, in MiB. If this is 0 or negative the default will be used.
@@ -73,13 +71,11 @@
     private VirtualMachineConfig(
             @NonNull String apkPath,
             @NonNull Signature[] certs,
-            @NonNull String idsigPath,
             @NonNull String payloadConfigPath,
             boolean debugMode,
             int memoryMib) {
         mApkPath = apkPath;
         mCerts = certs;
-        mIdsigPath = idsigPath;
         mPayloadConfigPath = payloadConfigPath;
         mDebugMode = debugMode;
         mMemoryMib = memoryMib;
@@ -106,18 +102,13 @@
             certList.add(new Signature(s));
         }
         Signature[] certs = certList.toArray(new Signature[0]);
-        final String idsigPath = b.getString(KEY_IDSIGPATH);
-        if (idsigPath == null) {
-            throw new VirtualMachineException("No idsigPath");
-        }
         final String payloadConfigPath = b.getString(KEY_PAYLOADCONFIGPATH);
         if (payloadConfigPath == null) {
             throw new VirtualMachineException("No payloadConfigPath");
         }
         final boolean debugMode = b.getBoolean(KEY_DEBUGMODE);
         final int memoryMib = b.getInt(KEY_MEMORY_MIB);
-        return new VirtualMachineConfig(
-                apkPath, certs, idsigPath, payloadConfigPath, debugMode, memoryMib);
+        return new VirtualMachineConfig(apkPath, certs, payloadConfigPath, debugMode, memoryMib);
     }
 
     /** Persists this config to a stream, for example a file. */
@@ -131,7 +122,6 @@
         }
         String[] certs = certList.toArray(new String[0]);
         b.putStringArray(KEY_CERTS, certs);
-        b.putString(KEY_IDSIGPATH, mIdsigPath);
         b.putString(KEY_PAYLOADCONFIGPATH, mPayloadConfigPath);
         b.putBoolean(KEY_DEBUGMODE, mDebugMode);
         if (mMemoryMib > 0) {
@@ -171,7 +161,6 @@
     /* package */ VirtualMachineAppConfig toParcel() throws FileNotFoundException {
         VirtualMachineAppConfig parcel = new VirtualMachineAppConfig();
         parcel.apk = ParcelFileDescriptor.open(new File(mApkPath), MODE_READ_ONLY);
-        parcel.idsig = ParcelFileDescriptor.open(new File(mIdsigPath), MODE_READ_ONLY);
         parcel.configPath = mPayloadConfigPath;
         parcel.debug = mDebugMode;
         parcel.memoryMib = mMemoryMib;
@@ -184,7 +173,6 @@
         private String mPayloadConfigPath;
         private boolean mDebugMode;
         private int mMemoryMib;
-        private String mIdsigPath; // TODO(jiyong): remove this
         // TODO(jiyong): add more items like # of cpu, size of ram, debuggability, etc.
 
         /** Creates a builder for the given context (APK), and the payload config file in APK. */
@@ -209,14 +197,6 @@
             return this;
         }
 
-        // TODO(jiyong): remove this. Apps shouldn't need to set the path to the idsig file. It
-        // should be automatically found or created on demand.
-        /** Set the path to the idsig file for the current application. */
-        public Builder idsigPath(@NonNull String idsigPath) {
-            mIdsigPath = idsigPath;
-            return this;
-        }
-
         /** Builds an immutable {@link VirtualMachineConfig} */
         public @NonNull VirtualMachineConfig build() {
             final String apkPath = mContext.getPackageCodePath();
@@ -235,7 +215,7 @@
             }
 
             return new VirtualMachineConfig(
-                    apkPath, certs, mIdsigPath, mPayloadConfigPath, mDebugMode, mMemoryMib);
+                    apkPath, certs, mPayloadConfigPath, mDebugMode, mMemoryMib);
         }
     }
 }
diff --git a/tests/testapk/src/java/com/android/microdroid/test/TestActivity.java b/tests/testapk/src/java/com/android/microdroid/test/TestActivity.java
index f73772e..ad34ca4 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/TestActivity.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/TestActivity.java
@@ -37,7 +37,6 @@
         try {
             VirtualMachineConfig config =
                     new VirtualMachineConfig.Builder(this, "assets/vm_config.json")
-                            .idsigPath("/data/local/tmp/virt/MicrodroidTestApp.apk.idsig")
                             .build();
 
             VirtualMachineManager vmm = VirtualMachineManager.getInstance(this);
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index cf92d5a..c3b36ee 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -26,6 +26,7 @@
         "libanyhow",
         "libcommand_fds",
         "libdisk",
+        "libidsig",
         "liblog_rust",
         "libmicrodroid_metadata",
         "libmicrodroid_payload_config",
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
index 8affaad..d136465 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice/IVirtualizationService.aidl
@@ -35,6 +35,15 @@
     void initializeWritablePartition(in ParcelFileDescriptor imageFd, long size);
 
     /**
+     * Create or update an idsig file that digests the given APK file. The idsig file follows the
+     * idsig format that is defined by the APK Signature Scheme V4. The idsig file is not updated
+     * when it is up to date with the input file, which is checked by comparing the
+     * signing_info.apk_digest field in the idsig file with the signer.signed_data.digests.digest
+     * field in the input APK file.
+     */
+    void createOrUpdateIdsigFile(in ParcelFileDescriptor inputFd, in ParcelFileDescriptor idsigFd);
+
+    /**
      * Get a list of all currently running VMs. This method is only intended for debug purposes,
      * and as such is only permitted from the shell user.
      */
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 96e3c44..23a9c03 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -37,6 +37,7 @@
 };
 use anyhow::{bail, Context, Result};
 use disk::QcowFile;
+use idsig::{V4Signature, HashAlgorithm};
 use log::{debug, error, warn, info};
 use microdroid_payload_config::VmPayloadConfig;
 use std::convert::TryInto;
@@ -209,6 +210,24 @@
         Ok(())
     }
 
+    /// Creates or update the idsig file by digesting the input APK file.
+    fn createOrUpdateIdsigFile(
+        &self,
+        input_fd: &ParcelFileDescriptor,
+        idsig_fd: &ParcelFileDescriptor,
+    ) -> binder::Result<()> {
+        // TODO(b/193504400): do this only when (1) idsig_fd is empty or (2) the APK digest in
+        // idsig_fd is different from APK digest in input_fd
+
+        let mut input = clone_file(input_fd)?;
+        let mut sig = V4Signature::create(&mut input, 4096, &[], HashAlgorithm::SHA256).unwrap();
+
+        let mut output = clone_file(idsig_fd)?;
+        output.set_len(0).unwrap();
+        sig.write_into(&mut output).unwrap();
+        Ok(())
+    }
+
     /// Get a list of all currently running VMs. This method is only intended for debug purposes,
     /// and as such is only permitted from the shell user.
     fn debugListVms(&self) -> binder::Result<Vec<VirtualMachineDebugInfo>> {
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 5b3f193..8db43fb 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -49,7 +49,14 @@
     debug: bool,
 ) -> Result<(), Error> {
     let apk_file = File::open(apk).context("Failed to open APK file")?;
+    let idsig_file = File::create(idsig).context("Failed to create idsig file")?;
+
+    let apk_fd = ParcelFileDescriptor::new(apk_file);
+    let idsig_fd = ParcelFileDescriptor::new(idsig_file);
+    service.createOrUpdateIdsigFile(&apk_fd, &idsig_fd)?;
+
     let idsig_file = File::open(idsig).context("Failed to open idsig file")?;
+    let idsig_fd = ParcelFileDescriptor::new(idsig_file);
 
     if !instance.exists() {
         const INSTANCE_FILE_SIZE: u64 = 10 * 1024 * 1024;
@@ -57,8 +64,8 @@
     }
 
     let config = VirtualMachineConfig::AppConfig(VirtualMachineAppConfig {
-        apk: ParcelFileDescriptor::new(apk_file).into(),
-        idsig: ParcelFileDescriptor::new(idsig_file).into(),
+        apk: apk_fd.into(),
+        idsig: idsig_fd.into(),
         instanceImage: open_parcel_file(instance, true /* writable */)?.into(),
         configPath: config_path.to_owned(),
         debug,