Merge "apkdmverity: dm-verity over apk"
diff --git a/apkverity/.cargo/config.toml b/apkverity/.cargo/config.toml
new file mode 100644
index 0000000..fac6997
--- /dev/null
+++ b/apkverity/.cargo/config.toml
@@ -0,0 +1,3 @@
+# Configuring loop devices and device mampper require root privilege.
+[target.x86_64-unknown-linux-gnu]
+runner = 'sudo -E'
diff --git a/apkverity/Cargo.toml b/apkverity/Cargo.toml
new file mode 100644
index 0000000..bd0ab01
--- /dev/null
+++ b/apkverity/Cargo.toml
@@ -0,0 +1,19 @@
+[package]
+name = "apkdmverity"
+version = "0.1.0"
+authors = ["Jiyong Park <jiyong@google.com>"]
+edition = "2018"
+
+[dependencies]
+anyhow = "1.0"
+scopeguard = "1.1"
+bitflags = "1.2"
+clap = "2.33"
+libc = "0.2"
+nix = "0.21"
+num-derive = "0.3"
+num-traits = "0.2"
+uuid = { version = "0.8", features = ["v4"] }
+
+[dev-dependencies]
+tempfile = "3.2"
diff --git a/apkverity/src/apksigv4.rs b/apkverity/src/apksigv4.rs
new file mode 100644
index 0000000..f1ee0a4
--- /dev/null
+++ b/apkverity/src/apksigv4.rs
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+use anyhow::{anyhow, Context, Result};
+use num_derive::FromPrimitive;
+use num_traits::FromPrimitive;
+use std::io::{Read, Seek};
+
+// `apksigv4` module provides routines to decode the idsig file as defined in [APK signature
+// scheme v4] (https://source.android.com/security/apksigning/v4).
+
+#[derive(Debug)]
+pub struct V4Signature {
+    pub version: Version,
+    pub hashing_info: HashingInfo,
+    pub signing_info: SigningInfo,
+    pub merkle_tree_size: u32,
+    pub merkle_tree_offset: u64,
+}
+
+#[derive(Debug)]
+pub struct HashingInfo {
+    pub hash_algorithm: HashAlgorithm,
+    pub log2_blocksize: u8,
+    pub salt: Box<[u8]>,
+    pub raw_root_hash: Box<[u8]>,
+}
+
+#[derive(Debug)]
+pub struct SigningInfo {
+    pub apk_digest: Box<[u8]>,
+    pub x509_certificate: Box<[u8]>,
+    pub additional_data: Box<[u8]>,
+    pub public_key: Box<[u8]>,
+    pub signature_algorithm_id: SignatureAlgorithmId,
+    pub signature: Box<[u8]>,
+}
+
+#[derive(Debug, PartialEq, FromPrimitive)]
+#[repr(u32)]
+pub enum Version {
+    V2 = 2,
+}
+
+impl Version {
+    fn from(val: u32) -> Result<Version> {
+        Self::from_u32(val).ok_or(anyhow!("{} is an unsupported version", val))
+    }
+}
+
+#[derive(Debug, PartialEq, FromPrimitive)]
+#[repr(u32)]
+pub enum HashAlgorithm {
+    SHA256 = 1,
+}
+
+impl HashAlgorithm {
+    fn from(val: u32) -> Result<HashAlgorithm> {
+        Self::from_u32(val).ok_or(anyhow!("{} is an unsupported hash algorithm", val))
+    }
+}
+
+#[derive(Debug, PartialEq, FromPrimitive)]
+#[allow(non_camel_case_types)]
+#[repr(u32)]
+pub enum SignatureAlgorithmId {
+    RSASSA_PSS_SHA2_256 = 0x0101,
+    RSASSA_PSS_SHA2_512 = 0x0102,
+    RSASSA_PKCS1_SHA2_256 = 0x0103,
+    RSASSA_PKCS1_SHA2_512 = 0x0104,
+    ECDSA_SHA2_256 = 0x0201,
+    ECDSA_SHA2_512 = 0x0202,
+    DSA_SHA2_256 = 0x0301,
+}
+
+impl SignatureAlgorithmId {
+    fn from(val: u32) -> Result<SignatureAlgorithmId> {
+        Self::from_u32(val)
+            .with_context(|| format!("{:#06x} is an unsupported signature algorithm", val))
+    }
+}
+
+impl V4Signature {
+    /// Reads a stream from `r` and then parses it into a `V4Signature` struct.
+    pub fn from<T: Read + Seek>(mut r: T) -> Result<V4Signature> {
+        Ok(V4Signature {
+            version: Version::from(read_le_u32(&mut r)?)?,
+            hashing_info: HashingInfo::from(&mut r)?,
+            signing_info: SigningInfo::from(&mut r)?,
+            merkle_tree_size: read_le_u32(&mut r)?,
+            merkle_tree_offset: r.stream_position()?,
+        })
+    }
+}
+
+impl HashingInfo {
+    fn from(mut r: &mut dyn Read) -> Result<HashingInfo> {
+        read_le_u32(&mut r)?;
+        Ok(HashingInfo {
+            hash_algorithm: HashAlgorithm::from(read_le_u32(&mut r)?)?,
+            log2_blocksize: read_u8(&mut r)?,
+            salt: read_sized_array(&mut r)?,
+            raw_root_hash: read_sized_array(&mut r)?,
+        })
+    }
+}
+
+impl SigningInfo {
+    fn from(mut r: &mut dyn Read) -> Result<SigningInfo> {
+        read_le_u32(&mut r)?;
+        Ok(SigningInfo {
+            apk_digest: read_sized_array(&mut r)?,
+            x509_certificate: read_sized_array(&mut r)?,
+            additional_data: read_sized_array(&mut r)?,
+            public_key: read_sized_array(&mut r)?,
+            signature_algorithm_id: SignatureAlgorithmId::from(read_le_u32(&mut r)?)?,
+            signature: read_sized_array(&mut r)?,
+        })
+    }
+}
+
+fn read_u8(r: &mut dyn Read) -> Result<u8> {
+    let mut byte = [0; 1];
+    r.read_exact(&mut byte)?;
+    Ok(byte[0])
+}
+
+fn read_le_u32(r: &mut dyn Read) -> Result<u32> {
+    let mut bytes = [0; 4];
+    r.read_exact(&mut bytes)?;
+    Ok(u32::from_le_bytes(bytes))
+}
+
+fn read_sized_array(r: &mut dyn Read) -> Result<Box<[u8]>> {
+    let size = read_le_u32(r)?;
+    let mut data = vec![0; size as usize];
+    r.read_exact(&mut data)?;
+    Ok(data.into_boxed_slice())
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use std::io::Cursor;
+
+    fn hexstring_from(s: &[u8]) -> String {
+        s.iter().map(|byte| format!("{:02x}", byte)).reduce(|i, j| i + &j).unwrap_or(String::new())
+    }
+
+    #[test]
+    fn parse_idsig_file() {
+        let idsig = Cursor::new(include_bytes!("../testdata/test.apk.idsig"));
+        let parsed = V4Signature::from(idsig).unwrap();
+
+        assert_eq!(Version::V2, parsed.version);
+
+        let hi = parsed.hashing_info;
+        assert_eq!(HashAlgorithm::SHA256, hi.hash_algorithm);
+        assert_eq!(12, hi.log2_blocksize);
+        assert_eq!("", hexstring_from(hi.salt.as_ref()));
+        assert_eq!(
+            "ce1194fdb3cb2537daf0ac8cdf4926754adcbce5abeece7945fe25d204a0df6a",
+            hexstring_from(hi.raw_root_hash.as_ref())
+        );
+
+        let si = parsed.signing_info;
+        assert_eq!(
+            "b5225523a813fb84ed599dd649698c080bcfed4fb19ddb00283a662a2683bc15",
+            hexstring_from(si.apk_digest.as_ref())
+        );
+        assert_eq!("", hexstring_from(si.additional_data.as_ref()));
+        assert_eq!(
+            "303d021c77304d0f4732a90372bbfce095223e4ba82427ceb381f69bc6762d78021d008b99924\
+                   a8585c38d7f654835eb219ae9e176b44e86dcb23153e3d9d6",
+            hexstring_from(si.signature.as_ref())
+        );
+        assert_eq!(SignatureAlgorithmId::DSA_SHA2_256, si.signature_algorithm_id);
+
+        assert_eq!(36864, parsed.merkle_tree_size);
+        assert_eq!(2251, parsed.merkle_tree_offset);
+    }
+}
diff --git a/apkverity/src/dm.rs b/apkverity/src/dm.rs
new file mode 100644
index 0000000..d1cd2eb
--- /dev/null
+++ b/apkverity/src/dm.rs
@@ -0,0 +1,185 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// `dm` module implements part of the `device-mapper` ioctl interfaces. It currently supports
+// creation and deletion of the mapper device. It doesn't support other operations like querying
+// the status of the mapper device. And there's no plan to extend the support unless it is
+// required.
+//
+// Why in-house development? [`devicemapper`](https://crates.io/crates/devicemapper) is a public
+// Rust implementation of the device mapper APIs. However, it doesn't provide any abstraction for
+// the target-specific tables. User has to manually craft the table. Ironically, the library
+// provides a lot of APIs for the features that are not required for `apkdmverity` such as listing
+// the device mapper block devices that are currently listed in the kernel. Size is an important
+// criteria for Microdroid.
+
+use crate::util::*;
+
+use anyhow::Result;
+use std::fs::{File, OpenOptions};
+use std::io::Write;
+use std::mem::size_of;
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use uuid::Uuid;
+
+mod sys;
+mod verity;
+use sys::*;
+pub use verity::*;
+
+nix::ioctl_readwrite!(_dm_dev_create, DM_IOCTL, Cmd::DM_DEV_CREATE, DmIoctl);
+nix::ioctl_readwrite!(_dm_dev_remove, DM_IOCTL, Cmd::DM_DEV_REMOVE, DmIoctl);
+nix::ioctl_readwrite!(_dm_dev_suspend, DM_IOCTL, Cmd::DM_DEV_SUSPEND, DmIoctl);
+nix::ioctl_readwrite!(_dm_table_load, DM_IOCTL, Cmd::DM_TABLE_LOAD, DmIoctl);
+
+fn dm_dev_create(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+    // state of this process in any way.
+    Ok(unsafe { _dm_dev_create(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+fn dm_dev_remove(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+    // state of this process in any way.
+    Ok(unsafe { _dm_dev_remove(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+fn dm_dev_suspend(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+    // state of this process in any way.
+    Ok(unsafe { _dm_dev_suspend(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+fn dm_table_load(dm: &DeviceMapper, ioctl: *mut DmIoctl) -> Result<i32> {
+    // SAFETY: `ioctl` is copied into the kernel. It modifies the state in the kernel, not the
+    // state of this process in any way.
+    Ok(unsafe { _dm_table_load(dm.0.as_raw_fd(), ioctl) }?)
+}
+
+// `DmTargetSpec` is the header of the data structure for a device-mapper target. When doing the
+// ioctl, one of more `DmTargetSpec` (and its body) are appened to the `DmIoctl` struct.
+#[repr(C)]
+struct DmTargetSpec {
+    sector_start: u64,
+    length: u64, // number of 512 sectors
+    status: i32,
+    next: u32,
+    target_type: [u8; DM_MAX_TYPE_NAME],
+}
+
+impl DmTargetSpec {
+    fn new(target_type: &str) -> Result<Self> {
+        // SAFETY: zero initialized C struct is safe
+        let mut spec = unsafe { std::mem::MaybeUninit::<Self>::zeroed().assume_init() };
+        spec.target_type.as_mut().write_all(target_type.as_bytes())?;
+        Ok(spec)
+    }
+
+    fn as_u8_slice(&self) -> &[u8; size_of::<Self>()] {
+        // SAFETY: lifetime of the output reference isn't changed.
+        unsafe { std::mem::transmute::<&Self, &[u8; size_of::<Self>()]>(&self) }
+    }
+}
+
+impl DmIoctl {
+    fn new(name: &str) -> Result<DmIoctl> {
+        // SAFETY: zero initialized C struct is safe
+        let mut data = unsafe { std::mem::MaybeUninit::<Self>::zeroed().assume_init() };
+        data.version[0] = DM_VERSION_MAJOR;
+        data.version[1] = DM_VERSION_MINOR;
+        data.version[2] = DM_VERSION_PATCHLEVEL;
+        data.data_size = size_of::<Self>() as u32;
+        data.data_start = 0;
+        data.name.as_mut().write_all(name.as_bytes())?;
+        Ok(data)
+    }
+
+    fn set_uuid(&mut self, uuid: &str) -> Result<()> {
+        let mut dst = self.uuid.as_mut();
+        dst.fill(0);
+        dst.write_all(uuid.as_bytes())?;
+        Ok(())
+    }
+
+    fn as_u8_slice(&self) -> &[u8; size_of::<Self>()] {
+        // SAFETY: lifetime of the output reference isn't changed.
+        unsafe { std::mem::transmute::<&Self, &[u8; size_of::<Self>()]>(&self) }
+    }
+}
+
+/// `DeviceMapper` is the entry point for the device mapper framework. It essentially is a file
+/// handle to "/dev/mapper/control".
+pub struct DeviceMapper(File);
+
+impl DeviceMapper {
+    /// Constructs a new `DeviceMapper` entrypoint. This is essentially the same as opening
+    /// "/dev/mapper/control".
+    pub fn new() -> Result<DeviceMapper> {
+        let f = OpenOptions::new().read(true).write(true).open("/dev/mapper/control")?;
+        Ok(DeviceMapper(f))
+    }
+
+    /// Creates a device mapper device and configure it according to the `target` specification.
+    /// The path to the generated device is "/dev/mapper/<name>".
+    pub fn create_device(&self, name: &str, target: &DmVerityTarget) -> Result<PathBuf> {
+        // Step 1: create an empty device
+        let mut data = DmIoctl::new(&name)?;
+        data.set_uuid(&uuid())?;
+        dm_dev_create(&self, &mut data)?;
+
+        // Step 2: load table onto the device
+        let payload_size = size_of::<DmIoctl>() + target.as_u8_slice().len();
+
+        let mut data = DmIoctl::new(&name)?;
+        data.data_size = payload_size as u32;
+        data.data_start = size_of::<DmIoctl>() as u32;
+        data.target_count = 1;
+        data.flags |= Flag::DM_READONLY_FLAG;
+
+        let mut payload = Vec::with_capacity(payload_size);
+        payload.extend_from_slice(&data.as_u8_slice()[..]);
+        payload.extend_from_slice(&target.as_u8_slice()[..]);
+        dm_table_load(&self, payload.as_mut_ptr() as *mut DmIoctl)?;
+
+        // Step 3: activate the device (note: the term 'suspend' might be misleading, but it
+        // actually activates the table. See include/uapi/linux/dm-ioctl.h
+        let mut data = DmIoctl::new(&name)?;
+        dm_dev_suspend(&self, &mut data)?;
+
+        // Step 4: wait unti the device is created and return the device path
+        let path = Path::new("/dev/mapper").join(&name);
+        wait_for_path(&path)?;
+        Ok(path)
+    }
+
+    /// Removes a mapper device
+    pub fn delete_device_deferred(&self, name: &str) -> Result<()> {
+        let mut data = DmIoctl::new(&name)?;
+        data.flags |= Flag::DM_DEFERRED_REMOVE;
+        dm_dev_remove(&self, &mut data)?;
+        Ok(())
+    }
+}
+
+/// Used to derive a UUID that uniquely identifies a device mapper device when creating it.
+// TODO(jiyong): the v4 is a randomly generated UUID. We might want another version of UUID (e.g.
+// v3) where we can specify the namespace so that we can easily identify UUID's created for this
+// purpose. For now, this random UUID is fine because we are expected to have only "one" instance
+// of dm-verity device in Microdroid.
+fn uuid() -> String {
+    String::from(Uuid::new_v4().to_hyphenated().encode_lower(&mut Uuid::encode_buffer()))
+}
diff --git a/apkverity/src/dm/sys.rs b/apkverity/src/dm/sys.rs
new file mode 100644
index 0000000..f623a2b
--- /dev/null
+++ b/apkverity/src/dm/sys.rs
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+use bitflags::bitflags;
+
+// UAPI for device mapper can be found at include/uapi/linux/dm-ioctl.h
+
+pub const DM_IOCTL: u8 = 0xfd;
+
+#[repr(u16)]
+#[allow(non_camel_case_types)]
+#[allow(dead_code)]
+pub enum Cmd {
+    DM_VERSION = 0,
+    DM_REMOVE_ALL,
+    DM_LIST_DEVICES,
+    DM_DEV_CREATE,
+    DM_DEV_REMOVE,
+    DM_DEV_RENAME,
+    DM_DEV_SUSPEND,
+    DM_DEV_STATUS,
+    DM_DEV_WAIT,
+    DM_TABLE_LOAD,
+    DM_TABLE_CLEAR,
+    DM_TABLE_DEPS,
+    DM_TABLE_STATUS,
+    DM_LIST_VERSIONS,
+    DM_TARGET_MSG,
+    DM_DEV_SET_GEOMETRY,
+}
+
+#[repr(C)]
+pub struct DmIoctl {
+    pub version: [u32; 3],
+    pub data_size: u32,
+    pub data_start: u32,
+    pub target_count: u32,
+    pub open_count: i32,
+    pub flags: Flag,
+    pub event_nr: u32,
+    pub padding: u32,
+    pub dev: u64,
+    pub name: [u8; DM_NAME_LEN],
+    pub uuid: [u8; DM_UUID_LEN],
+    pub data: [u8; 7],
+}
+
+pub const DM_VERSION_MAJOR: u32 = 4;
+pub const DM_VERSION_MINOR: u32 = 0;
+pub const DM_VERSION_PATCHLEVEL: u32 = 0;
+
+pub const DM_NAME_LEN: usize = 128;
+pub const DM_UUID_LEN: usize = 129;
+pub const DM_MAX_TYPE_NAME: usize = 16;
+
+bitflags! {
+    pub struct Flag: u32 {
+        const DM_READONLY_FLAG = 1 << 0;
+        const DM_SUSPEND_FLAG = 1 << 1;
+        const DM_PERSISTENT_DEV_FLAG = 1 << 3;
+        const DM_STATUS_TABLE_FLAG = 1 << 4;
+        const DM_ACTIVE_PRESENT_FLAG = 1 << 5;
+        const DM_INACTIVE_PRESENT_FLAG = 1 << 6;
+        const DM_BUFFER_FULL_FLAG = 1 << 8;
+        const DM_SKIP_BDGET_FLAG = 1 << 9;
+        const DM_SKIP_LOCKFS_FLAG = 1 << 10;
+        const DM_NOFLUSH_FLAG = 1 << 11;
+        const DM_QUERY_INACTIVE_TABLE_FLAG = 1 << 12;
+        const DM_UEVENT_GENERATED_FLAG = 1 << 13;
+        const DM_UUID_FLAG = 1 << 14;
+        const DM_SECURE_DATA_FLAG = 1 << 15;
+        const DM_DATA_OUT_FLAG = 1 << 16;
+        const DM_DEFERRED_REMOVE = 1 << 17;
+        const DM_INTERNAL_SUSPEND_FLAG = 1 << 18;
+    }
+}
diff --git a/apkverity/src/dm/verity.rs b/apkverity/src/dm/verity.rs
new file mode 100644
index 0000000..cfc9504
--- /dev/null
+++ b/apkverity/src/dm/verity.rs
@@ -0,0 +1,195 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// `dm::verity` module implements the "verity" target in the device mapper framework. Specifically,
+// it provides `DmVerityTargetBuilder` struct which is used to construct a `DmVerityTarget` struct
+// which is then given to `DeviceMapper` to create a mapper device.
+
+use anyhow::{bail, Context, Result};
+use std::io::Write;
+use std::mem::size_of;
+use std::path::Path;
+
+use super::DmTargetSpec;
+use crate::util::*;
+
+// The UAPI for the verity target is here.
+// https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
+
+/// Version of the verity target spec. Only `V1` is supported.
+pub enum DmVerityVersion {
+    V1,
+}
+
+/// The hash algorithm to use. SHA256 and SHA512 are supported.
+pub enum DmVerityHashAlgorithm {
+    SHA256,
+    SHA512,
+}
+
+/// A builder that constructs `DmVerityTarget` struct.
+pub struct DmVerityTargetBuilder<'a> {
+    version: DmVerityVersion,
+    data_device: Option<&'a Path>,
+    data_size: u64,
+    hash_device: Option<&'a Path>,
+    hash_algorithm: DmVerityHashAlgorithm,
+    root_digest: Option<&'a [u8]>,
+    salt: Option<&'a [u8]>,
+}
+
+pub struct DmVerityTarget(Box<[u8]>);
+
+impl DmVerityTarget {
+    pub fn as_u8_slice(&self) -> &[u8] {
+        self.0.as_ref()
+    }
+}
+
+impl<'a> Default for DmVerityTargetBuilder<'a> {
+    fn default() -> Self {
+        DmVerityTargetBuilder {
+            version: DmVerityVersion::V1,
+            data_device: None,
+            data_size: 0,
+            hash_device: None,
+            hash_algorithm: DmVerityHashAlgorithm::SHA256,
+            root_digest: None,
+            salt: None,
+        }
+    }
+}
+
+impl<'a> DmVerityTargetBuilder<'a> {
+    /// Sets the device that will be used as the data device (i.e. providing actual data).
+    pub fn data_device(&mut self, p: &'a Path, size: u64) -> &mut Self {
+        self.data_device = Some(p);
+        self.data_size = size;
+        self
+    }
+
+    /// Sets the device that provides the merkle tree.
+    pub fn hash_device(&mut self, p: &'a Path) -> &mut Self {
+        self.hash_device = Some(p);
+        self
+    }
+
+    /// Sets the hash algorithm that the merkel tree is using.
+    pub fn hash_algorithm(&mut self, algo: DmVerityHashAlgorithm) -> &mut Self {
+        self.hash_algorithm = algo;
+        self
+    }
+
+    /// Sets the root digest of the merkle tree. The format is hexadecimal string.
+    pub fn root_digest(&mut self, digest: &'a [u8]) -> &mut Self {
+        self.root_digest = Some(digest);
+        self
+    }
+
+    /// Sets the salt used when creating the merkle tree. Note that this is empty for merkle trees
+    /// created following the APK signature scheme V4.
+    pub fn salt(&mut self, salt: &'a [u8]) -> &mut Self {
+        self.salt = Some(salt);
+        self
+    }
+
+    /// Constructs a `DmVerityTarget`.
+    pub fn build(&self) -> Result<DmVerityTarget> {
+        // The `DmVerityTarget` struct actually is a flattened data consisting of a header and
+        // body. The format of the header is `dm_target_spec` as defined in
+        // include/uapi/linux/dm-ioctl.h. The format of the body, in case of `verity` target is
+        // https://www.kernel.org/doc/Documentation/device-mapper/verity.txt
+        //
+        // Step 1: check the validity of the inputs and extra additional data (e.g. block size)
+        // from them.
+        let version = match self.version {
+            DmVerityVersion::V1 => 1,
+        };
+
+        let data_device_path = self
+            .data_device
+            .context("data device is not set")?
+            .to_str()
+            .context("data device path is not encoded in utf8")?;
+        let stat = fstat(self.data_device.unwrap())?; // safe; checked just above
+        let data_block_size = stat.st_blksize as u64;
+        let data_size = self.data_size;
+        let num_data_blocks = data_size / data_block_size;
+
+        let hash_device_path = self
+            .hash_device
+            .context("hash device is not set")?
+            .to_str()
+            .context("hash device path is not encoded in utf8")?;
+        let stat = fstat(self.data_device.unwrap())?; // safe; checked just above
+        let hash_block_size = stat.st_blksize;
+
+        let hash_algorithm = match self.hash_algorithm {
+            DmVerityHashAlgorithm::SHA256 => "sha256",
+            DmVerityHashAlgorithm::SHA512 => "sha512",
+        };
+
+        let root_digest = if let Some(root_digest) = self.root_digest {
+            hexstring_from(root_digest)
+        } else {
+            bail!("root digest is not set")
+        };
+
+        let salt = if self.salt.is_none() || self.salt.unwrap().is_empty() {
+            "-".to_string() // Note. It's not an empty string!
+        } else {
+            hexstring_from(self.salt.unwrap())
+        };
+
+        // Step2: serialize the information according to the spec, which is ...
+        // DmTargetSpec{...}
+        // <version> <dev> <hash_dev>
+        // <data_block_size> <hash_block_size>
+        // <num_data_blocks> <hash_start_block>
+        // <algorithm> <digest> <salt>
+        // [<#opt_params> <opt_params>]
+        // null terminator
+
+        // TODO(jiyong): support the optional parameters... if needed.
+        let mut body = String::new();
+        use std::fmt::Write;
+        write!(&mut body, "{} ", version)?;
+        write!(&mut body, "{} ", data_device_path)?;
+        write!(&mut body, "{} ", hash_device_path)?;
+        write!(&mut body, "{} ", data_block_size)?;
+        write!(&mut body, "{} ", hash_block_size)?;
+        write!(&mut body, "{} ", num_data_blocks)?;
+        write!(&mut body, "{} ", 0)?; // hash_start_block
+        write!(&mut body, "{} ", hash_algorithm)?;
+        write!(&mut body, "{} ", root_digest)?;
+        write!(&mut body, "{}", salt)?;
+        write!(&mut body, "\0")?; // null terminator
+
+        let size = size_of::<DmTargetSpec>() + body.len();
+        let aligned_size = (size + 7) & !7; // align to 8 byte boundaries
+        let padding = aligned_size - size;
+        let mut header = DmTargetSpec::new("verity")?;
+        header.sector_start = 0;
+        header.length = data_size / 512; // number of 512-byte sectors
+        header.next = aligned_size as u32;
+
+        let mut buf = Vec::with_capacity(aligned_size);
+        buf.write_all(header.as_u8_slice())?;
+        buf.write_all(body.as_bytes())?;
+        buf.write_all(vec![0; padding].as_slice())?;
+        Ok(DmVerityTarget(buf.into_boxed_slice()))
+    }
+}
diff --git a/apkverity/src/loopdevice.rs b/apkverity/src/loopdevice.rs
new file mode 100644
index 0000000..bb0e767
--- /dev/null
+++ b/apkverity/src/loopdevice.rs
@@ -0,0 +1,136 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+// `loopdevice` module provides `attach` and `detach` functions that are for attaching and
+// detaching a regular file to and from a loop device. Note that
+// `loopdev`(https://crates.io/crates/loopdev) is a public alternative to this. In-house
+// implementation was chosen to make Android-specific changes (like the use of the new
+// LOOP_CONFIGURE instead of the legacy LOOP_SET_FD + LOOP_SET_STATUS64 combo which is considerably
+// slower than the former).
+
+mod sys;
+
+use anyhow::{Context, Result};
+use std::fs::{File, OpenOptions};
+use std::os::unix::io::AsRawFd;
+use std::path::{Path, PathBuf};
+use std::thread;
+use std::time::{Duration, Instant};
+
+use crate::loopdevice::sys::*;
+use crate::util::*;
+
+// These are old-style ioctls, thus *_bad.
+nix::ioctl_none_bad!(_loop_ctl_get_free, LOOP_CTL_GET_FREE);
+nix::ioctl_write_ptr_bad!(_loop_configure, LOOP_CONFIGURE, loop_config);
+nix::ioctl_none_bad!(_loop_clr_fd, LOOP_CLR_FD);
+
+fn loop_ctl_get_free(ctrl_file: &File) -> Result<i32> {
+    // SAFETY: this ioctl changes the state in kernel, but not the state in this process.
+    // The returned device number is a global resource; not tied to this process. So, we don't
+    // need to keep track of it.
+    Ok(unsafe { _loop_ctl_get_free(ctrl_file.as_raw_fd()) }?)
+}
+
+fn loop_configure(device_file: &File, config: &loop_config) -> Result<i32> {
+    // SAFETY: this ioctl changes the state in kernel, but not the state in this process.
+    Ok(unsafe { _loop_configure(device_file.as_raw_fd(), config) }?)
+}
+
+fn loop_clr_fd(device_file: &File) -> Result<i32> {
+    // SAFETY: this ioctl disassociates the loop device with `device_file`, where the FD will
+    // remain opened afterward. The association itself is kept for open FDs.
+    Ok(unsafe { _loop_clr_fd(device_file.as_raw_fd()) }?)
+}
+
+/// Creates a loop device and attach the given file at `path` as the backing store.
+pub fn attach<P: AsRef<Path>>(path: P, offset: u64, size_limit: u64) -> Result<PathBuf> {
+    // Attaching a file to a loop device can make a race condition; a loop device number obtained
+    // from LOOP_CTL_GET_FREE might have been used by another thread or process. In that case the
+    // subsequet LOOP_CONFIGURE ioctl returns with EBUSY. Try until it succeeds.
+    //
+    // Note that the timing parameters below are chosen rather arbitrarily. In practice (i.e.
+    // inside Microdroid) we can't experience the race condition because `apkverity` is the only
+    // user of /dev/loop-control at the moment. This loop is mostly for testing where multiple
+    // tests run concurrently.
+    const TIMEOUT: Duration = Duration::from_secs(1);
+    const INTERVAL: Duration = Duration::from_millis(10);
+
+    let begin = Instant::now();
+    loop {
+        match try_attach(&path, offset, size_limit) {
+            Ok(loop_dev) => return Ok(loop_dev),
+            Err(e) => {
+                if begin.elapsed() > TIMEOUT {
+                    return Err(e);
+                }
+            }
+        };
+        thread::sleep(INTERVAL);
+    }
+}
+
+fn try_attach<P: AsRef<Path>>(path: P, offset: u64, size_limit: u64) -> Result<PathBuf> {
+    // Get a free loop device
+    wait_for_path(LOOP_CONTROL)?;
+    let ctrl_file = OpenOptions::new()
+        .read(true)
+        .write(true)
+        .open(LOOP_CONTROL)
+        .context("Failed to open loop control")?;
+    let num = loop_ctl_get_free(&ctrl_file).context("Failed to get free loop device")?;
+
+    // Construct the loop_config struct
+    let backing_file = OpenOptions::new()
+        .read(true)
+        .open(&path)
+        .context(format!("failed to open {:?}", path.as_ref()))?;
+    // SAFETY: zero initialized C structs is safe
+    let mut config = unsafe { std::mem::MaybeUninit::<loop_config>::zeroed().assume_init() };
+    config.fd = backing_file.as_raw_fd() as u32;
+    config.block_size = 4096;
+    config.info.lo_offset = offset;
+    config.info.lo_sizelimit = size_limit;
+    config.info.lo_flags |= Flag::LO_FLAGS_DIRECT_IO | Flag::LO_FLAGS_READ_ONLY;
+
+    // Special case: don't use direct IO when the backing file is already a loop device, which
+    // happens only during test. DirectIO-on-loop-over-loop makes the outer loop device
+    // unaccessible.
+    #[cfg(test)]
+    if path.as_ref().to_str().unwrap().starts_with("/dev/loop") {
+        config.info.lo_flags.remove(Flag::LO_FLAGS_DIRECT_IO);
+    }
+
+    // Configure the loop device to attach the backing file
+    let device_path = format!("/dev/loop{}", num);
+    wait_for_path(&device_path)?;
+    let device_file = OpenOptions::new()
+        .read(true)
+        .write(true)
+        .open(&device_path)
+        .context(format!("failed to open {:?}", &device_path))?;
+    loop_configure(&device_file, &mut config)
+        .context(format!("Failed to configure {:?}", &device_path))?;
+
+    Ok(PathBuf::from(device_path))
+}
+
+/// Detaches backing file from the loop device `path`.
+pub fn detach<P: AsRef<Path>>(path: P) -> Result<()> {
+    let device_file = OpenOptions::new().read(true).write(true).open(&path)?;
+    loop_clr_fd(&device_file)?;
+    Ok(())
+}
diff --git a/apkverity/src/loopdevice/sys.rs b/apkverity/src/loopdevice/sys.rs
new file mode 100644
index 0000000..2d4977b
--- /dev/null
+++ b/apkverity/src/loopdevice/sys.rs
@@ -0,0 +1,64 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+use bitflags::bitflags;
+
+// This UAPI is copied and converted from include/uapi/linux/loop.h Note that this module doesn't
+// implement all the features introduced in loop(4). Only the features that are required to support
+// the `apkdmverity` use cases are implemented.
+
+pub const LOOP_CONTROL: &str = "/dev/loop-control";
+
+pub const LOOP_CTL_GET_FREE: libc::c_ulong = 0x4C82;
+pub const LOOP_CONFIGURE: libc::c_ulong = 0x4C0A;
+pub const LOOP_CLR_FD: libc::c_ulong = 0x4C01;
+
+#[repr(C)]
+pub struct loop_config {
+    pub fd: u32,
+    pub block_size: u32,
+    pub info: loop_info64,
+    pub reserved: [u64; 8],
+}
+
+#[repr(C)]
+pub struct loop_info64 {
+    pub lo_device: u64,
+    pub lo_inode: u64,
+    pub lo_rdevice: u64,
+    pub lo_offset: u64,
+    pub lo_sizelimit: u64,
+    pub lo_number: u32,
+    pub lo_encrypt_type: u32,
+    pub lo_encrypt_key_size: u32,
+    pub lo_flags: Flag,
+    pub lo_file_name: [u8; LO_NAME_SIZE],
+    pub lo_crypt_name: [u8; LO_NAME_SIZE],
+    pub lo_encrypt_key: [u8; LO_KEY_SIZE],
+    pub lo_init: [u64; 2],
+}
+
+bitflags! {
+    pub struct Flag: u32 {
+        const LO_FLAGS_READ_ONLY = 1 << 0;
+        const LO_FLAGS_AUTOCLEAR = 1 << 2;
+        const LO_FLAGS_PARTSCAN = 1 << 3;
+        const LO_FLAGS_DIRECT_IO = 1 << 4;
+    }
+}
+
+pub const LO_NAME_SIZE: usize = 64;
+pub const LO_KEY_SIZE: usize = 32;
diff --git a/apkverity/src/main.rs b/apkverity/src/main.rs
new file mode 100644
index 0000000..6fe12a0
--- /dev/null
+++ b/apkverity/src/main.rs
@@ -0,0 +1,303 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+//! `apkdmverity` is a program that protects a signed APK file using dm-verity. The APK is assumed
+//! to be signed using APK signature scheme V4. The idsig file generated by the signing scheme is
+//! also used as an input to provide the merkle tree. This program is currently intended to be used
+//! to securely mount the APK inside Microdroid. Since the APK is physically stored in the file
+//! system managed by the host Android which is assumed to be compromisable, it is important to
+//! keep the integrity of the file "inside" Microdroid.
+
+mod apksigv4;
+mod dm;
+mod loopdevice;
+mod util;
+
+use crate::apksigv4::*;
+
+use anyhow::{bail, Context, Result};
+use clap::{App, Arg};
+use std::fmt::Debug;
+use std::fs;
+use std::fs::File;
+use std::os::unix::fs::FileTypeExt;
+use std::path::{Path, PathBuf};
+
+fn main() -> Result<()> {
+    let matches = App::new("apkverity")
+        .about("Creates a dm-verity block device out of APK signed with APK signature scheme V4.")
+        .arg(
+            Arg::with_name("apk")
+                .help("Input APK file. Must be signed using the APK signature scheme V4.")
+                .required(true),
+        )
+        .arg(
+            Arg::with_name("idsig")
+                .help("The idsig file having the merkle tree and the signing info.")
+                .required(true),
+        )
+        .arg(
+            Arg::with_name("name")
+                .help(
+                    "Name of the dm-verity block device. The block device is created at \
+                      \"/dev/mapper/<name>\".",
+                )
+                .required(true),
+        )
+        .get_matches();
+
+    let apk = matches.value_of("apk").unwrap();
+    let idsig = matches.value_of("idsig").unwrap();
+    let name = matches.value_of("name").unwrap();
+    enable_verity(apk, idsig, name)?;
+    Ok(())
+}
+
+struct VerityResult {
+    data_device: PathBuf,
+    hash_device: PathBuf,
+    mapper_device: PathBuf,
+}
+
+const BLOCK_SIZE: u64 = 4096;
+
+// Makes a dm-verity block device out of `apk` and its accompanying `idsig` files.
+fn enable_verity<P: AsRef<Path> + Debug>(apk: P, idsig: P, name: &str) -> Result<VerityResult> {
+    // Attach the apk file to a loop device if the apk file is a regular file. If not (i.e. block
+    // device), we only need to get the size and use the block device as it is.
+    let (data_device, apk_size) = if fs::metadata(&apk)?.file_type().is_block_device() {
+        (apk.as_ref().to_path_buf(), util::blkgetsize64(apk.as_ref())?)
+    } else {
+        let apk_size = fs::metadata(&apk)?.len();
+        if apk_size % BLOCK_SIZE != 0 {
+            bail!("The size of {:?} is not multiple of {}.", &apk, BLOCK_SIZE)
+        }
+        (loopdevice::attach(&apk, 0, apk_size)?, apk_size)
+    };
+
+    // Parse the idsig file to locate the merkle tree in it, then attach the file to a loop device
+    // with the offset so that the start of the merkle tree becomes the beginning of the loop
+    // device.
+    let sig = V4Signature::from(File::open(&idsig)?)?;
+    let offset = sig.merkle_tree_offset;
+    let size = sig.merkle_tree_size as u64;
+    let hash_device = loopdevice::attach(&idsig, offset, size)?;
+
+    // Build a dm-verity target spec from the information from the idsig file. The apk and the
+    // idsig files are used as the data device and the hash device, respectively.
+    let target = dm::DmVerityTargetBuilder::default()
+        .data_device(&data_device, apk_size)
+        .hash_device(&hash_device)
+        .root_digest(&sig.hashing_info.raw_root_hash)
+        .hash_algorithm(match sig.hashing_info.hash_algorithm {
+            apksigv4::HashAlgorithm::SHA256 => dm::DmVerityHashAlgorithm::SHA256,
+        })
+        .salt(&sig.hashing_info.salt)
+        .build()
+        .context(format!("Merkle tree in {:?} is not compatible with dm-verity", &idsig))?;
+
+    // Actually create a dm-verity block device using the spec.
+    let dm = dm::DeviceMapper::new()?;
+    let mapper_device =
+        dm.create_device(&name, &target).context("Failed to create dm-verity device")?;
+
+    Ok(VerityResult { data_device, hash_device, mapper_device })
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::*;
+    use std::fs::OpenOptions;
+    use std::io::{Cursor, Write};
+    use std::os::unix::fs::FileExt;
+
+    struct TestContext<'a> {
+        data_backing_file: &'a Path,
+        hash_backing_file: &'a Path,
+        result: &'a VerityResult,
+    }
+
+    fn create_block_aligned_file(path: &Path, data: &[u8]) {
+        let mut f = File::create(&path).unwrap();
+        f.write_all(data).unwrap();
+
+        // Add padding so that the size of the file is multiple of 4096.
+        let aligned_size = (data.len() as u64 + BLOCK_SIZE - 1) & !(BLOCK_SIZE - 1);
+        let padding = aligned_size - data.len() as u64;
+        f.write_all(vec![0; padding as usize].as_slice()).unwrap();
+    }
+
+    fn prepare_inputs(test_dir: &Path, apk: &[u8], idsig: &[u8]) -> (PathBuf, PathBuf) {
+        let apk_path = test_dir.join("test.apk");
+        let idsig_path = test_dir.join("test.apk.idsig");
+        create_block_aligned_file(&apk_path, apk);
+        create_block_aligned_file(&idsig_path, idsig);
+        (apk_path, idsig_path)
+    }
+
+    fn run_test(apk: &[u8], idsig: &[u8], name: &str, check: fn(TestContext)) {
+        let test_dir = tempfile::TempDir::new().unwrap();
+        let (apk_path, idsig_path) = prepare_inputs(&test_dir.path(), apk, idsig);
+
+        // Run the program and register clean-ups.
+        let ret = enable_verity(&apk_path, &idsig_path, name).unwrap();
+        let ret = scopeguard::guard(ret, |ret| {
+            loopdevice::detach(ret.data_device).unwrap();
+            loopdevice::detach(ret.hash_device).unwrap();
+            let dm = dm::DeviceMapper::new().unwrap();
+            dm.delete_device_deferred(name).unwrap();
+        });
+
+        check(TestContext {
+            data_backing_file: &apk_path,
+            hash_backing_file: &idsig_path,
+            result: &ret,
+        });
+    }
+
+    #[test]
+    fn correct_inputs() {
+        let apk = include_bytes!("../testdata/test.apk");
+        let idsig = include_bytes!("../testdata/test.apk.idsig");
+        run_test(apk.as_ref(), idsig.as_ref(), "correct", |ctx| {
+            let verity = fs::read(&ctx.result.mapper_device).unwrap();
+            let original = fs::read(&ctx.result.data_device).unwrap();
+            assert_eq!(verity.len(), original.len()); // fail fast
+            assert_eq!(verity.as_slice(), original.as_slice());
+        });
+    }
+
+    // A single byte change in the APK file causes an IO error
+    #[test]
+    fn incorrect_apk() {
+        let apk = include_bytes!("../testdata/test.apk");
+        let idsig = include_bytes!("../testdata/test.apk.idsig");
+
+        let mut modified_apk = Vec::new();
+        modified_apk.extend_from_slice(apk);
+        if let Some(byte) = modified_apk.get_mut(100) {
+            *byte = 1;
+        }
+
+        run_test(modified_apk.as_slice(), idsig.as_ref(), "incorrect_apk", |ctx| {
+            let ret = fs::read(&ctx.result.mapper_device).map_err(|e| e.kind());
+            assert_eq!(ret, Err(std::io::ErrorKind::Other));
+        });
+    }
+
+    // A single byte change in the merkle tree also causes an IO error
+    #[test]
+    fn incorrect_merkle_tree() {
+        let apk = include_bytes!("../testdata/test.apk");
+        let idsig = include_bytes!("../testdata/test.apk.idsig");
+
+        // Make a single-byte change to the merkle tree
+        let offset = V4Signature::from(Cursor::new(&idsig)).unwrap().merkle_tree_offset as usize;
+
+        let mut modified_idsig = Vec::new();
+        modified_idsig.extend_from_slice(idsig);
+        if let Some(byte) = modified_idsig.get_mut(offset + 10) {
+            *byte = 1;
+        }
+
+        run_test(apk.as_ref(), modified_idsig.as_slice(), "incorrect_merkle_tree", |ctx| {
+            let ret = fs::read(&ctx.result.mapper_device).map_err(|e| e.kind());
+            assert_eq!(ret, Err(std::io::ErrorKind::Other));
+        });
+    }
+
+    // APK is not altered when the verity device is created, but later modified. IO error should
+    // occur when trying to read the data around the modified location. This is the main scenario
+    // that we'd like to protect.
+    #[test]
+    fn tampered_apk() {
+        let apk = include_bytes!("../testdata/test.apk");
+        let idsig = include_bytes!("../testdata/test.apk.idsig");
+
+        run_test(apk.as_ref(), idsig.as_ref(), "tampered_apk", |ctx| {
+            // At this moment, the verity device is created. Then let's change 10 bytes in the
+            // backing data file.
+            const MODIFIED_OFFSET: u64 = 10000;
+            let f = OpenOptions::new().read(true).write(true).open(ctx.data_backing_file).unwrap();
+            f.write_at(&[0, 1], MODIFIED_OFFSET).unwrap();
+
+            // Read around the modified location causes an error
+            let f = File::open(&ctx.result.mapper_device).unwrap();
+            let mut buf = vec![0; 10]; // just read 10 bytes
+            let ret = f.read_at(&mut buf, MODIFIED_OFFSET).map_err(|e| e.kind());
+            assert!(ret.is_err());
+            assert_eq!(ret, Err(std::io::ErrorKind::Other));
+        });
+    }
+
+    // idsig file is not alread when the verity device is created, but later modified. Unlike to
+    // the APK case, this doesn't occur IO error because the merkle tree is already cached.
+    #[test]
+    fn tampered_idsig() {
+        let apk = include_bytes!("../testdata/test.apk");
+        let idsig = include_bytes!("../testdata/test.apk.idsig");
+        run_test(apk.as_ref(), idsig.as_ref(), "tampered_idsig", |ctx| {
+            // Change 10 bytes in the merkle tree.
+            let f = OpenOptions::new().read(true).write(true).open(ctx.hash_backing_file).unwrap();
+            f.write_at(&[0, 10], 100).unwrap();
+
+            let verity = fs::read(&ctx.result.mapper_device).unwrap();
+            let original = fs::read(&ctx.result.data_device).unwrap();
+            assert_eq!(verity.len(), original.len());
+            assert_eq!(verity.as_slice(), original.as_slice());
+        });
+    }
+
+    // test if both files are already block devices
+    #[test]
+    fn inputs_are_block_devices() {
+        use std::ops::Deref;
+        let apk = include_bytes!("../testdata/test.apk");
+        let idsig = include_bytes!("../testdata/test.apk.idsig");
+
+        let test_dir = tempfile::TempDir::new().unwrap();
+        let (apk_path, idsig_path) = prepare_inputs(&test_dir.path(), apk, idsig);
+
+        // attach the files to loop devices to make them block devices
+        let apk_size = fs::metadata(&apk_path).unwrap().len();
+        let idsig_size = fs::metadata(&idsig_path).unwrap().len();
+
+        // Note that apk_loop_device is not detatched. This is because, when the apk file is
+        // already a block device, `enable_verity` uses the block device as it is. The detatching
+        // of the data device is done in the scopeguard for the return value of `enable_verity`
+        // below. Only the idsig_loop_device needs detatching.
+        let apk_loop_device = loopdevice::attach(&apk_path, 0, apk_size).unwrap();
+        let idsig_loop_device =
+            scopeguard::guard(loopdevice::attach(&idsig_path, 0, idsig_size).unwrap(), |dev| {
+                loopdevice::detach(dev).unwrap()
+            });
+
+        let name = "loop_as_input";
+        // Run the program WITH the loop devices, not the regular files.
+        let ret = enable_verity(apk_loop_device.deref(), idsig_loop_device.deref(), &name).unwrap();
+        let ret = scopeguard::guard(ret, |ret| {
+            loopdevice::detach(ret.data_device).unwrap();
+            loopdevice::detach(ret.hash_device).unwrap();
+            let dm = dm::DeviceMapper::new().unwrap();
+            dm.delete_device_deferred(name).unwrap();
+        });
+
+        let verity = fs::read(&ret.mapper_device).unwrap();
+        let original = fs::read(&apk_path).unwrap();
+        assert_eq!(verity.len(), original.len()); // fail fast
+        assert_eq!(verity.as_slice(), original.as_slice());
+    }
+}
diff --git a/apkverity/src/util.rs b/apkverity/src/util.rs
new file mode 100644
index 0000000..415e99b
--- /dev/null
+++ b/apkverity/src/util.rs
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2021 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.
+ */
+
+use anyhow::{bail, Result};
+use nix::sys::stat::FileStat;
+use std::fs;
+use std::fs::File;
+use std::os::unix::fs::FileTypeExt;
+use std::os::unix::io::AsRawFd;
+use std::path::Path;
+use std::thread;
+use std::time::{Duration, Instant};
+
+/// Returns when the file exists on the given `path` or timeout (1s) occurs.
+pub fn wait_for_path<P: AsRef<Path>>(path: P) -> Result<()> {
+    const TIMEOUT: Duration = Duration::from_secs(1);
+    const INTERVAL: Duration = Duration::from_millis(10);
+    let begin = Instant::now();
+    while !path.as_ref().exists() {
+        if begin.elapsed() > TIMEOUT {
+            bail!("{:?} not found. TIMEOUT.", path.as_ref());
+        }
+        thread::sleep(INTERVAL);
+    }
+    Ok(())
+}
+
+/// Returns hexadecimal reprentation of a given byte array.
+pub fn hexstring_from(s: &[u8]) -> String {
+    s.iter().map(|byte| format!("{:02x}", byte)).reduce(|i, j| i + &j).unwrap_or(String::new())
+}
+
+/// fstat that accepts a path rather than FD
+pub fn fstat(p: &Path) -> Result<FileStat> {
+    let f = File::open(p)?;
+    Ok(nix::sys::stat::fstat(f.as_raw_fd())?)
+}
+
+// From include/uapi/linux/fs.h
+const BLK: u8 = 0x12;
+const BLKGETSIZE64: u8 = 114;
+nix::ioctl_read!(_blkgetsize64, BLK, BLKGETSIZE64, libc::size_t);
+
+/// Gets the size of a block device
+pub fn blkgetsize64(p: &Path) -> Result<u64> {
+    let f = File::open(p)?;
+    if !f.metadata()?.file_type().is_block_device() {
+        bail!("{:?} is not a block device", p);
+    }
+    let mut size: usize = 0;
+    // SAFETY: kernel copies the return value out to `size`. The file is kept open until the end of
+    // this function.
+    unsafe { _blkgetsize64(f.as_raw_fd(), &mut size) }?;
+    Ok(size as u64)
+}
diff --git a/apkverity/testdata/README b/apkverity/testdata/README
new file mode 100644
index 0000000..b219b24
--- /dev/null
+++ b/apkverity/testdata/README
@@ -0,0 +1,24 @@
+The test data is generated as follows:
+
+$ keytool -keystore keystore -genkey -alias mykey
+The password for the keystore and the key is 123456.
+
+The signer information is set as follows:
+
+CN=Android, OU=Android, O=Android, L=Mountain View, ST=CA, C=US
+
+Build a random apk (Bluetooth.apk is chosen arbitrary)
+
+$ m Bluetooth
+$ cp $ANDROID_PRODUCT_OUT/system/app/Bluetooth.apk ./test.apk
+
+Sign it using the apksigner.
+
+$ m apksigner
+$ apksigner sign --ks keystore ./test.apk
+
+Check that the idsig file is created.
+
+$ ls -l test.apk*
+-rw-r----- 1 jiyong primarygroup 3888734 Jun  4 01:08 test.apk
+-rw-r----- 1 jiyong primarygroup   39115 Jun  4 01:08 test.apk.idsig
diff --git a/apkverity/testdata/keystore b/apkverity/testdata/keystore
new file mode 100644
index 0000000..3a4f4a7
--- /dev/null
+++ b/apkverity/testdata/keystore
Binary files differ
diff --git a/apkverity/testdata/test.apk b/apkverity/testdata/test.apk
new file mode 100644
index 0000000..cbee532
--- /dev/null
+++ b/apkverity/testdata/test.apk
Binary files differ
diff --git a/apkverity/testdata/test.apk.idsig b/apkverity/testdata/test.apk.idsig
new file mode 100644
index 0000000..8c112de
--- /dev/null
+++ b/apkverity/testdata/test.apk.idsig
Binary files differ