Merge changes from topic "idsig"

* changes:
  idsig: Include the APK digest
  apkverify: Add function to pick v4 apk digest
  apkverify: Rank based on v4 preferences
diff --git a/apex/Android.bp b/apex/Android.bp
index 4698088..0f30c67 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -4,9 +4,9 @@
 
 microdroid_filesystem_images = [
     "microdroid_super",
-    "microdroid_boot",
+    "microdroid_boot-5.10",
     "microdroid_init_boot",
-    "microdroid_vendor_boot",
+    "microdroid_vendor_boot-5.10",
     "microdroid_vbmeta",
     "microdroid_vbmeta_bootconfig",
 ]
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index e314b71..1c0714e 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -361,8 +361,8 @@
 virt_apex_files = {
     'bootloader.pubkey': 'etc/microdroid_bootloader.avbpubkey',
     'bootloader': 'etc/microdroid_bootloader',
-    'boot.img': 'etc/fs/microdroid_boot.img',
-    'vendor_boot.img': 'etc/fs/microdroid_vendor_boot.img',
+    'boot.img': 'etc/fs/microdroid_boot-5.10.img',
+    'vendor_boot.img': 'etc/fs/microdroid_vendor_boot-5.10.img',
     'init_boot.img': 'etc/fs/microdroid_init_boot.img',
     'super.img': 'etc/fs/microdroid_super.img',
     'vbmeta.img': 'etc/fs/microdroid_vbmeta.img',
diff --git a/authfs/Android.bp b/authfs/Android.bp
index 935ed5c..84eb0f4 100644
--- a/authfs/Android.bp
+++ b/authfs/Android.bp
@@ -13,7 +13,6 @@
         "authfs_aidl_interface-rust",
         "libandroid_logger",
         "libanyhow",
-        "libauthfs_crypto_bindgen",
         "libauthfs_fsverity_metadata",
         "libbinder_rpc_unstable_bindgen",
         "libbinder_rs",
@@ -23,6 +22,7 @@
         "liblibc",
         "liblog_rust",
         "libnix",
+        "libopenssl",
         "libprotobuf",
         "libstructopt",
         "libthiserror",
@@ -34,26 +34,11 @@
         },
     },
     shared_libs: [
-        "libcrypto",
         "libbinder_rpc_unstable",
     ],
     defaults: ["crosvm_defaults"],
 }
 
-// TODO(b/172687320): remove once there is a canonical bindgen.
-rust_bindgen {
-    name: "libauthfs_crypto_bindgen",
-    wrapper_src: "src/crypto.hpp",
-    crate_name: "authfs_crypto_bindgen",
-    source_stem: "bindings",
-    shared_libs: [
-        "libcrypto",
-    ],
-    bindgen_flags: ["--size_t-is-usize"],
-    cflags: ["-D BORINGSSL_NO_CXX"],
-    apex_available: ["com.android.virt"],
-}
-
 rust_binary {
     name: "authfs",
     defaults: ["authfs_defaults"],
@@ -80,13 +65,3 @@
         "testdata/input.4m.fsv_meta.bad_merkle",
     ],
 }
-
-rust_test {
-    name: "libauthfs_crypto_bindgen_test",
-    srcs: [":libauthfs_crypto_bindgen"],
-    crate_name: "authfs_crypto_bindgen_test",
-    test_suites: ["general-tests"],
-    auto_gen_config: true,
-    clippy_lints: "none",
-    lints: "none",
-}
diff --git a/authfs/src/crypto.hpp b/authfs/src/crypto.hpp
deleted file mode 100644
index 58b0bd3..0000000
--- a/authfs/src/crypto.hpp
+++ /dev/null
@@ -1,22 +0,0 @@
-/*
- * Copyright (C) 2020 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.
- */
-
-#ifndef AUTHFS_OPENSSL_WRAPPER_H
-#define AUTHFS_OPENSSL_WRAPPER_H
-
-#include <openssl/sha.h>
-
-#endif  // AUTHFS_OPENSSL_WRAPPER_H
diff --git a/authfs/src/crypto.rs b/authfs/src/crypto.rs
deleted file mode 100644
index 672dfb6..0000000
--- a/authfs/src/crypto.rs
+++ /dev/null
@@ -1,122 +0,0 @@
-/*
- * Copyright (C) 2020 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 std::mem::MaybeUninit;
-
-use thiserror::Error;
-
-#[derive(Error, Debug)]
-pub enum CryptoError {
-    #[error("Unexpected error returned from {0}")]
-    Unexpected(&'static str),
-}
-
-use authfs_crypto_bindgen::{SHA256_Final, SHA256_Init, SHA256_Update, SHA256_CTX};
-
-pub type Sha256Hash = [u8; Sha256Hasher::HASH_SIZE];
-
-pub struct Sha256Hasher {
-    ctx: SHA256_CTX,
-}
-
-impl Sha256Hasher {
-    pub const HASH_SIZE: usize = 32;
-
-    pub const HASH_OF_4096_ZEROS: [u8; Self::HASH_SIZE] = [
-        0xad, 0x7f, 0xac, 0xb2, 0x58, 0x6f, 0xc6, 0xe9, 0x66, 0xc0, 0x04, 0xd7, 0xd1, 0xd1, 0x6b,
-        0x02, 0x4f, 0x58, 0x05, 0xff, 0x7c, 0xb4, 0x7c, 0x7a, 0x85, 0xda, 0xbd, 0x8b, 0x48, 0x89,
-        0x2c, 0xa7,
-    ];
-
-    pub fn new() -> Result<Sha256Hasher, CryptoError> {
-        // Safe assuming the crypto FFI should initialize the uninitialized `ctx`, which is
-        // currently a pure data struct.
-        unsafe {
-            let mut ctx = MaybeUninit::uninit();
-            if SHA256_Init(ctx.as_mut_ptr()) == 0 {
-                Err(CryptoError::Unexpected("SHA256_Init"))
-            } else {
-                Ok(Sha256Hasher { ctx: ctx.assume_init() })
-            }
-        }
-    }
-
-    pub fn update(&mut self, data: &[u8]) -> Result<&mut Self, CryptoError> {
-        // Safe assuming the crypto FFI will not touch beyond `ctx` as pure data.
-        let retval = unsafe {
-            SHA256_Update(&mut self.ctx, data.as_ptr() as *mut std::ffi::c_void, data.len())
-        };
-        if retval == 0 {
-            Err(CryptoError::Unexpected("SHA256_Update"))
-        } else {
-            Ok(self)
-        }
-    }
-
-    pub fn update_from<I, T>(&mut self, iter: I) -> Result<&mut Self, CryptoError>
-    where
-        I: IntoIterator<Item = T>,
-        T: AsRef<[u8]>,
-    {
-        for data in iter {
-            self.update(data.as_ref())?;
-        }
-        Ok(self)
-    }
-
-    pub fn finalize(&mut self) -> Result<[u8; Self::HASH_SIZE], CryptoError> {
-        let mut md = [0u8; Self::HASH_SIZE];
-        // Safe assuming the crypto FFI will not touch beyond `ctx` as pure data.
-        let retval = unsafe { SHA256_Final(md.as_mut_ptr(), &mut self.ctx) };
-        if retval == 0 {
-            Err(CryptoError::Unexpected("SHA256_Final"))
-        } else {
-            Ok(md)
-        }
-    }
-}
-
-#[cfg(test)]
-mod tests {
-    use super::*;
-
-    fn to_hex_string(data: &[u8]) -> String {
-        data.iter().map(|&b| format!("{:02x}", b)).collect()
-    }
-
-    #[test]
-    fn verify_hash_values() -> Result<(), CryptoError> {
-        let hash = Sha256Hasher::new()?.update(&[0; 0])?.finalize()?;
-        let s: String = to_hex_string(&hash);
-        assert_eq!(s, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
-
-        let hash = Sha256Hasher::new()?
-            .update(&[1u8; 1])?
-            .update(&[2u8; 1])?
-            .update(&[3u8; 1])?
-            .finalize()?;
-        let s: String = to_hex_string(&hash);
-        assert_eq!(s, "039058c6f2c0cb492c533b0a4d14ef77cc0f78abccced5287d84a1a2011cfb81");
-        Ok(())
-    }
-
-    #[test]
-    fn sha256_of_4096_zeros() -> Result<(), CryptoError> {
-        let hash = Sha256Hasher::new()?.update(&[0u8; 4096])?.finalize()?;
-        assert_eq!(hash, Sha256Hasher::HASH_OF_4096_ZEROS);
-        Ok(())
-    }
-}
diff --git a/authfs/src/fsverity/builder.rs b/authfs/src/fsverity/builder.rs
index fda47bc..8585fdf 100644
--- a/authfs/src/fsverity/builder.rs
+++ b/authfs/src/fsverity/builder.rs
@@ -14,13 +14,20 @@
  * limitations under the License.
  */
 
-use super::common::{build_fsverity_digest, merkle_tree_height, FsverityError};
+use super::common::{
+    build_fsverity_digest, merkle_tree_height, FsverityError, Sha256Hash, SHA256_HASH_SIZE,
+};
 use crate::common::{divide_roundup, CHUNK_SIZE};
-use crate::crypto::{CryptoError, Sha256Hash, Sha256Hasher};
+use openssl::sha::Sha256;
 
-const HASH_SIZE: usize = Sha256Hasher::HASH_SIZE;
+const HASH_SIZE: usize = SHA256_HASH_SIZE;
 const HASH_PER_PAGE: usize = CHUNK_SIZE as usize / HASH_SIZE;
 
+const HASH_OF_4096_ZEROS: Sha256Hash = [
+    0xad, 0x7f, 0xac, 0xb2, 0x58, 0x6f, 0xc6, 0xe9, 0x66, 0xc0, 0x04, 0xd7, 0xd1, 0xd1, 0x6b, 0x02,
+    0x4f, 0x58, 0x05, 0xff, 0x7c, 0xb4, 0x7c, 0x7a, 0x85, 0xda, 0xbd, 0x8b, 0x48, 0x89, 0x2c, 0xa7,
+];
+
 /// MerkleLeaves can be used by the class' customer for bookkeeping integrity data for their bytes.
 /// It can also be used to generate the standard fs-verity digest for the source data.
 ///
@@ -34,12 +41,17 @@
     file_size: u64,
 }
 
-fn hash_all_pages(source: &[Sha256Hash]) -> Result<Vec<Sha256Hash>, CryptoError> {
+fn hash_all_pages(source: &[Sha256Hash]) -> Vec<Sha256Hash> {
     source
         .chunks(HASH_PER_PAGE)
         .map(|chunk| {
             let padding_bytes = (HASH_PER_PAGE - chunk.len()) * HASH_SIZE;
-            Sha256Hasher::new()?.update_from(chunk)?.update(&vec![0u8; padding_bytes])?.finalize()
+            let mut ctx = Sha256::new();
+            for data in chunk {
+                ctx.update(data.as_ref());
+            }
+            ctx.update(&vec![0u8; padding_bytes]);
+            ctx.finish()
         })
         .collect()
 }
@@ -64,7 +76,7 @@
     pub fn resize(&mut self, new_file_size: usize) {
         let new_file_size = new_file_size as u64;
         let leaves_size = divide_roundup(new_file_size, CHUNK_SIZE);
-        self.leaves.resize(leaves_size as usize, Sha256Hasher::HASH_OF_4096_ZEROS);
+        self.leaves.resize(leaves_size as usize, HASH_OF_4096_ZEROS);
         self.file_size = new_file_size;
     }
 
@@ -75,7 +87,7 @@
         if self.leaves.len() < index + 1 {
             // When resizing, fill in hash of zeros by default. This makes it easy to handle holes
             // in a file.
-            self.leaves.resize(index + 1, Sha256Hasher::HASH_OF_4096_ZEROS);
+            self.leaves.resize(index + 1, HASH_OF_4096_ZEROS);
         }
         self.leaves[index].clone_from_slice(hash);
 
@@ -116,9 +128,8 @@
 
                 // `leaves` is owned and can't be the initial state below. Here we manually hash it
                 // first to avoid a copy and to get the type right.
-                let second_level = hash_all_pages(&self.leaves)?;
-                let hashes =
-                    (1..=level).try_fold(second_level, |source, _| hash_all_pages(&source))?;
+                let second_level = hash_all_pages(&self.leaves);
+                let hashes = (1..=level).fold(second_level, |source, _| hash_all_pages(&source));
                 if hashes.len() != 1 {
                     Err(FsverityError::InvalidState)
                 } else {
@@ -131,7 +142,7 @@
     /// Returns the fs-verity digest based on the current tree and file size.
     pub fn calculate_fsverity_digest(&self) -> Result<Sha256Hash, FsverityError> {
         let root_hash = self.calculate_root_hash()?;
-        Ok(build_fsverity_digest(&root_hash, self.file_size)?)
+        Ok(build_fsverity_digest(&root_hash, self.file_size))
     }
 }
 
@@ -143,6 +154,7 @@
     //  $ fsverity digest foo
     use super::*;
     use anyhow::Result;
+    use openssl::sha::sha256;
 
     #[test]
     fn merkle_tree_empty_file() -> Result<()> {
@@ -194,7 +206,7 @@
     #[test]
     fn merkle_tree_non_sequential() -> Result<()> {
         let mut tree = MerkleLeaves::new();
-        let hash = Sha256Hasher::new()?.update(&vec![1u8; CHUNK_SIZE as usize])?.finalize()?;
+        let hash = sha256(&vec![1u8; CHUNK_SIZE as usize]);
 
         // Update hashes of 4 1-blocks.
         tree.update_hash(1, &hash, CHUNK_SIZE * 2);
@@ -221,8 +233,8 @@
         assert!(tree.is_index_valid(1));
         assert!(tree.is_index_valid(2));
         assert!(!tree.is_index_valid(3));
-        assert!(tree.is_consistent(1, &Sha256Hasher::HASH_OF_4096_ZEROS));
-        assert!(tree.is_consistent(2, &Sha256Hasher::HASH_OF_4096_ZEROS));
+        assert!(tree.is_consistent(1, &HASH_OF_4096_ZEROS));
+        assert!(tree.is_consistent(2, &HASH_OF_4096_ZEROS));
         Ok(())
     }
 
@@ -240,17 +252,17 @@
         assert!(!tree.is_index_valid(2));
         // The second chunk is a hole and full of zero. When shrunk, with zero padding, the hash
         // happens to be consistent to a full-zero chunk.
-        assert!(tree.is_consistent(1, &Sha256Hasher::HASH_OF_4096_ZEROS));
+        assert!(tree.is_consistent(1, &HASH_OF_4096_ZEROS));
         Ok(())
     }
 
     fn generate_fsverity_digest_sequentially(test_data: &[u8]) -> Result<Sha256Hash> {
         let mut tree = MerkleLeaves::new();
         for (index, chunk) in test_data.chunks(CHUNK_SIZE as usize).enumerate() {
-            let hash = Sha256Hasher::new()?
-                .update(chunk)?
-                .update(&vec![0u8; CHUNK_SIZE as usize - chunk.len()])?
-                .finalize()?;
+            let mut ctx = Sha256::new();
+            ctx.update(chunk);
+            ctx.update(&vec![0u8; CHUNK_SIZE as usize - chunk.len()]);
+            let hash = ctx.finish();
 
             tree.update_hash(index, &hash, CHUNK_SIZE * index as u64 + chunk.len() as u64);
         }
diff --git a/authfs/src/fsverity/common.rs b/authfs/src/fsverity/common.rs
index eba379d..cb268ef 100644
--- a/authfs/src/fsverity/common.rs
+++ b/authfs/src/fsverity/common.rs
@@ -20,7 +20,13 @@
 
 use super::sys::{FS_VERITY_HASH_ALG_SHA256, FS_VERITY_LOG_BLOCKSIZE, FS_VERITY_VERSION};
 use crate::common::{divide_roundup, CHUNK_SIZE};
-use crate::crypto::{CryptoError, Sha256Hash, Sha256Hasher};
+use openssl::sha::Sha256;
+
+/// Output size of SHA-256 in bytes.
+pub const SHA256_HASH_SIZE: usize = 32;
+
+/// A SHA-256 hash.
+pub type Sha256Hash = [u8; SHA256_HASH_SIZE];
 
 #[derive(Error, Debug)]
 pub enum FsverityError {
@@ -32,8 +38,6 @@
     CannotVerify,
     #[error("I/O error")]
     Io(#[from] io::Error),
-    #[error("Crypto")]
-    UnexpectedCryptoError(#[from] CryptoError),
     #[error("Invalid state")]
     InvalidState,
 }
@@ -47,7 +51,7 @@
 
 /// Return the Merkle tree height for our tree configuration, or None if the size is 0.
 pub fn merkle_tree_height(data_size: u64) -> Option<u64> {
-    let hashes_per_node = CHUNK_SIZE / Sha256Hasher::HASH_SIZE as u64;
+    let hashes_per_node = CHUNK_SIZE / SHA256_HASH_SIZE as u64;
     let hash_pages = divide_roundup(data_size, hashes_per_node * CHUNK_SIZE);
     log128_ceil(hash_pages)
 }
@@ -56,7 +60,7 @@
 pub fn merkle_tree_size(mut data_size: u64) -> u64 {
     let mut total = 0;
     while data_size > CHUNK_SIZE {
-        let hash_size = divide_roundup(data_size, CHUNK_SIZE) * Sha256Hasher::HASH_SIZE as u64;
+        let hash_size = divide_roundup(data_size, CHUNK_SIZE) * SHA256_HASH_SIZE as u64;
         let hash_storage_size = divide_roundup(hash_size, CHUNK_SIZE) * CHUNK_SIZE;
         total += hash_storage_size;
         data_size = hash_storage_size;
@@ -64,28 +68,25 @@
     total
 }
 
-pub fn build_fsverity_digest(
-    root_hash: &Sha256Hash,
-    file_size: u64,
-) -> Result<Sha256Hash, CryptoError> {
+pub fn build_fsverity_digest(root_hash: &Sha256Hash, file_size: u64) -> Sha256Hash {
     // Little-endian byte representation of fsverity_descriptor from linux/fsverity.h
     // Not FFI-ed as it seems easier to deal with the raw bytes manually.
-    Sha256Hasher::new()?
-        .update(&FS_VERITY_VERSION.to_le_bytes())? // version
-        .update(&FS_VERITY_HASH_ALG_SHA256.to_le_bytes())? // hash_algorithm
-        .update(&FS_VERITY_LOG_BLOCKSIZE.to_le_bytes())? // log_blocksize
-        .update(&0u8.to_le_bytes())? // salt_size
-        .update(&0u32.to_le_bytes())? // sig_size
-        .update(&file_size.to_le_bytes())? // data_size
-        .update(root_hash)? // root_hash, first 32 bytes
-        .update(&[0u8; 32])? // root_hash, last 32 bytes, always 0 because we are using sha256.
-        .update(&[0u8; 32])? // salt
-        .update(&[0u8; 32])? // reserved
-        .update(&[0u8; 32])? // reserved
-        .update(&[0u8; 32])? // reserved
-        .update(&[0u8; 32])? // reserved
-        .update(&[0u8; 16])? // reserved
-        .finalize()
+    let mut hash = Sha256::new();
+    hash.update(&FS_VERITY_VERSION.to_le_bytes()); // version
+    hash.update(&FS_VERITY_HASH_ALG_SHA256.to_le_bytes()); // hash_algorithm
+    hash.update(&FS_VERITY_LOG_BLOCKSIZE.to_le_bytes()); // log_blocksize
+    hash.update(&0u8.to_le_bytes()); // salt_size
+    hash.update(&0u32.to_le_bytes()); // sig_size
+    hash.update(&file_size.to_le_bytes()); // data_size
+    hash.update(root_hash); // root_hash, first 32 bytes
+    hash.update(&[0u8; 32]); // root_hash, last 32 bytes, always 0 because we are using sha256.
+    hash.update(&[0u8; 32]); // salt
+    hash.update(&[0u8; 32]); // reserved
+    hash.update(&[0u8; 32]); // reserved
+    hash.update(&[0u8; 32]); // reserved
+    hash.update(&[0u8; 32]); // reserved
+    hash.update(&[0u8; 16]); // reserved
+    hash.finish()
 }
 
 #[cfg(test)]
diff --git a/authfs/src/fsverity/editor.rs b/authfs/src/fsverity/editor.rs
index 857c6d9..1e298be 100644
--- a/authfs/src/fsverity/editor.rs
+++ b/authfs/src/fsverity/editor.rs
@@ -56,17 +56,10 @@
 use std::sync::{Arc, RwLock};
 
 use super::builder::MerkleLeaves;
+use super::common::{Sha256Hash, SHA256_HASH_SIZE};
 use crate::common::{ChunkedSizeIter, CHUNK_SIZE};
-use crate::crypto::{CryptoError, Sha256Hash, Sha256Hasher};
 use crate::file::{ChunkBuffer, RandomWrite, ReadByChunk};
-
-// Implement the conversion from `CryptoError` to `io::Error` just to avoid manual error type
-// mapping below.
-impl From<CryptoError> for io::Error {
-    fn from(error: CryptoError) -> Self {
-        io::Error::new(io::ErrorKind::Other, error)
-    }
-}
+use openssl::sha::{sha256, Sha256};
 
 fn debug_assert_usize_is_u64() {
     // Since we don't need to support 32-bit CPU, make an assert to make conversion between
@@ -90,7 +83,7 @@
 
     /// Returns the fs-verity digest size in bytes.
     pub fn get_fsverity_digest_size(&self) -> usize {
-        Sha256Hasher::HASH_SIZE
+        SHA256_HASH_SIZE
     }
 
     /// Calculates the fs-verity digest of the current file.
@@ -119,7 +112,7 @@
             let size = self.read_backing_chunk_unverified(chunk_index, buf)?;
 
             // Ensure the returned buffer matches the known hash.
-            let hash = Sha256Hasher::new()?.update(buf)?.finalize()?;
+            let hash = sha256(buf);
             if !merkle_tree_locked.is_consistent(chunk_index as usize, &hash) {
                 return Err(io::Error::new(io::ErrorKind::InvalidData, "Inconsistent hash"));
             }
@@ -147,17 +140,17 @@
             self.read_backing_chunk_unverified(output_chunk_index as u64, &mut orig_data)?;
 
             // Verify original content
-            let hash = Sha256Hasher::new()?.update(&orig_data)?.finalize()?;
+            let hash = sha256(&orig_data);
             if !merkle_tree.is_consistent(output_chunk_index, &hash) {
                 return Err(io::Error::new(io::ErrorKind::InvalidData, "Inconsistent hash"));
             }
         }
 
-        Ok(Sha256Hasher::new()?
-            .update(&orig_data[..offset_from_alignment])?
-            .update(source)?
-            .update(&orig_data[offset_from_alignment + source.len()..])?
-            .finalize()?)
+        let mut ctx = Sha256::new();
+        ctx.update(&orig_data[..offset_from_alignment]);
+        ctx.update(source);
+        ctx.update(&orig_data[offset_from_alignment + source.len()..]);
+        Ok(ctx.finish())
     }
 
     fn new_chunk_hash(
@@ -171,7 +164,7 @@
         if current_size as u64 == CHUNK_SIZE {
             // Case 1: If the chunk is a complete one, just calculate the hash, regardless of
             // write location.
-            Ok(Sha256Hasher::new()?.update(source)?.finalize()?)
+            Ok(sha256(source))
         } else {
             // Case 2: For an incomplete write, calculate the hash based on previous data (if
             // any).
@@ -273,10 +266,10 @@
                 debug_assert!(new_tail_size <= s);
 
                 let zeros = vec![0; CHUNK_SIZE as usize - new_tail_size];
-                let new_hash = Sha256Hasher::new()?
-                    .update(&buf[..new_tail_size])?
-                    .update(&zeros)?
-                    .finalize()?;
+                let mut ctx = Sha256::new();
+                ctx.update(&buf[..new_tail_size]);
+                ctx.update(&zeros);
+                let new_hash = ctx.finish();
                 merkle_tree.update_hash(chunk_index as usize, &new_hash, size);
             }
         }
@@ -519,7 +512,7 @@
         // detects the inconsistent read.
         {
             let mut merkle_tree = file.merkle_tree.write().unwrap();
-            let overriding_hash = [42; Sha256Hasher::HASH_SIZE];
+            let overriding_hash = [42; SHA256_HASH_SIZE];
             merkle_tree.update_hash(0, &overriding_hash, 8192);
         }
         assert!(file.write_at(&[1; 1], 2048).is_err());
@@ -532,7 +525,7 @@
         // resumed write will fail since no bytes can be written due to the same inconsistency.
         {
             let mut merkle_tree = file.merkle_tree.write().unwrap();
-            let overriding_hash = [42; Sha256Hasher::HASH_SIZE];
+            let overriding_hash = [42; SHA256_HASH_SIZE];
             merkle_tree.update_hash(1, &overriding_hash, 8192);
         }
         assert_eq!(file.write_at(&[10; 8000], 0)?, 4096);
diff --git a/authfs/src/fsverity/metadata/Android.bp b/authfs/src/fsverity/metadata/Android.bp
index af3729f..c988884 100644
--- a/authfs/src/fsverity/metadata/Android.bp
+++ b/authfs/src/fsverity/metadata/Android.bp
@@ -18,7 +18,7 @@
     ],
     rustlibs: [
         "libauthfs_fsverity_metadata_bindgen",
-        "libring",
+        "libopenssl",
     ],
     edition: "2018",
     apex_available: ["com.android.virt"],
diff --git a/authfs/src/fsverity/metadata/metadata.rs b/authfs/src/fsverity/metadata/metadata.rs
index 8bc0617..54d0145 100644
--- a/authfs/src/fsverity/metadata/metadata.rs
+++ b/authfs/src/fsverity/metadata/metadata.rs
@@ -20,7 +20,7 @@
     FSVERITY_SIGNATURE_TYPE_NONE, FSVERITY_SIGNATURE_TYPE_PKCS7, FSVERITY_SIGNATURE_TYPE_RAW,
 };
 
-use ring::digest::{Context, SHA256};
+use openssl::sha::sha256;
 use std::cmp::min;
 use std::ffi::OsString;
 use std::fs::File;
@@ -96,14 +96,11 @@
 
         // Digest needs to be calculated with the raw value (without changing the endianness).
         let digest = match header.descriptor.hash_algorithm {
-            FSVERITY_HASH_ALG_SHA256 => {
-                let mut context = Context::new(&SHA256);
-                context.update(
-                    &back_buffer
-                        [DESCRIPTOR_OFFSET..DESCRIPTOR_OFFSET + size_of::<fsverity_descriptor>()],
-                );
-                Ok(context.finish().as_ref().to_owned())
-            }
+            FSVERITY_HASH_ALG_SHA256 => Ok(sha256(
+                &back_buffer
+                    [DESCRIPTOR_OFFSET..DESCRIPTOR_OFFSET + size_of::<fsverity_descriptor>()],
+            )
+            .to_vec()),
             alg => Err(io::Error::new(
                 io::ErrorKind::Other,
                 format!("Unsupported hash algorithm {}, continue (likely failing soon)", alg),
diff --git a/authfs/src/fsverity/verifier.rs b/authfs/src/fsverity/verifier.rs
index aaf4bf7..1434b7e 100644
--- a/authfs/src/fsverity/verifier.rs
+++ b/authfs/src/fsverity/verifier.rs
@@ -17,18 +17,21 @@
 use libc::EIO;
 use std::io;
 
-use super::common::{build_fsverity_digest, merkle_tree_height, FsverityError};
+use super::common::{build_fsverity_digest, merkle_tree_height, FsverityError, SHA256_HASH_SIZE};
 use crate::common::{divide_roundup, CHUNK_SIZE};
-use crate::crypto::{CryptoError, Sha256Hasher};
 use crate::file::{ChunkBuffer, ReadByChunk};
+use openssl::sha::{sha256, Sha256};
 
 const ZEROS: [u8; CHUNK_SIZE as usize] = [0u8; CHUNK_SIZE as usize];
 
-type HashBuffer = [u8; Sha256Hasher::HASH_SIZE];
+type HashBuffer = [u8; SHA256_HASH_SIZE];
 
-fn hash_with_padding(chunk: &[u8], pad_to: usize) -> Result<HashBuffer, CryptoError> {
+fn hash_with_padding(chunk: &[u8], pad_to: usize) -> HashBuffer {
     let padding_size = pad_to - chunk.len();
-    Sha256Hasher::new()?.update(chunk)?.update(&ZEROS[..padding_size])?.finalize()
+    let mut ctx = Sha256::new();
+    ctx.update(chunk);
+    ctx.update(&ZEROS[..padding_size]);
+    ctx.finish()
 }
 
 fn verity_check<T: ReadByChunk>(
@@ -42,7 +45,7 @@
     // beyond the file size, including empty file.
     assert_ne!(file_size, 0);
 
-    let chunk_hash = hash_with_padding(chunk, CHUNK_SIZE as usize)?;
+    let chunk_hash = hash_with_padding(chunk, CHUNK_SIZE as usize);
 
     // When the file is smaller or equal to CHUNK_SIZE, the root of Merkle tree is defined as the
     // hash of the file content, plus padding.
@@ -55,11 +58,11 @@
         |actual_hash, result| {
             let (merkle_chunk, hash_offset_in_chunk) = result?;
             let expected_hash =
-                &merkle_chunk[hash_offset_in_chunk..hash_offset_in_chunk + Sha256Hasher::HASH_SIZE];
+                &merkle_chunk[hash_offset_in_chunk..hash_offset_in_chunk + SHA256_HASH_SIZE];
             if actual_hash != expected_hash {
                 return Err(FsverityError::CannotVerify);
             }
-            Ok(hash_with_padding(&merkle_chunk, CHUNK_SIZE as usize)?)
+            Ok(hash_with_padding(&merkle_chunk, CHUNK_SIZE as usize))
         },
     )
 }
@@ -74,7 +77,7 @@
     file_size: u64,
     merkle_tree: &T,
 ) -> Result<impl Iterator<Item = Result<([u8; 4096], usize), FsverityError>> + '_, FsverityError> {
-    let hashes_per_node = CHUNK_SIZE / Sha256Hasher::HASH_SIZE as u64;
+    let hashes_per_node = CHUNK_SIZE / SHA256_HASH_SIZE as u64;
     debug_assert_eq!(hashes_per_node, 128u64);
     let max_level = merkle_tree_height(file_size).expect("file should not be empty") as u32;
     let root_to_leaf_steps = (0..=max_level)
@@ -85,7 +88,7 @@
             let leaves_size_per_node = leaves_size_per_hash * hashes_per_node;
             let nodes_at_level = divide_roundup(file_size, leaves_size_per_node);
             let level_size = nodes_at_level * CHUNK_SIZE;
-            let offset_in_level = (chunk_index / leaves_per_hash) * Sha256Hasher::HASH_SIZE as u64;
+            let offset_in_level = (chunk_index / leaves_per_hash) * SHA256_HASH_SIZE as u64;
             (level_size, offset_in_level)
         })
         .scan(0, |level_offset, (level_size, offset_in_level)| {
@@ -135,8 +138,8 @@
                 return Err(FsverityError::InsufficientData(size));
             }
         }
-        let root_hash = Sha256Hasher::new()?.update(&buf[..])?.finalize()?;
-        if expected_digest == build_fsverity_digest(&root_hash, file_size)? {
+        let root_hash = sha256(&buf[..]);
+        if expected_digest == build_fsverity_digest(&root_hash, file_size) {
             // Once verified, use the root_hash for verification going forward.
             Ok(VerifiedFileReader { chunked_file, file_size, merkle_tree, root_hash })
         } else {
diff --git a/authfs/src/main.rs b/authfs/src/main.rs
index 60318e8..c09ed71 100644
--- a/authfs/src/main.rs
+++ b/authfs/src/main.rs
@@ -42,7 +42,6 @@
 use structopt::StructOpt;
 
 mod common;
-mod crypto;
 mod file;
 mod fsstat;
 mod fsverity;
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index b55f3ab..fe31b27 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -78,7 +78,7 @@
         let apex_dir = Path::new(COMPOS_APEX_ROOT);
         let data_dir = Path::new(COMPOS_DATA_ROOT);
 
-        let config_apk = Self::locate_config_apk(apex_dir)?;
+        let config_apk = locate_config_apk(apex_dir)?;
         let apk_fd = File::open(config_apk).context("Failed to open config APK file")?;
         let apk_fd = ParcelFileDescriptor::new(apk_fd);
         let idsig_fd = prepare_idsig(service, &apk_fd, idsig)?;
@@ -135,27 +135,27 @@
         Ok(Self(instance))
     }
 
-    fn locate_config_apk(apex_dir: &Path) -> Result<PathBuf> {
-        // Our config APK will be in a directory under app, but the name of the directory is at the
-        // discretion of the build system. So just look in each sub-directory until we find it.
-        // (In practice there will be exactly one directory, so this shouldn't take long.)
-        let app_dir = apex_dir.join("app");
-        for dir in fs::read_dir(app_dir).context("Reading app dir")? {
-            let apk_file = dir?.path().join("CompOSPayloadApp.apk");
-            if apk_file.is_file() {
-                return Ok(apk_file);
-            }
-        }
-
-        bail!("Failed to locate CompOSPayloadApp.apk")
-    }
-
     /// Create and return an RPC Binder connection to the Comp OS service in the VM.
     pub fn get_service(&self) -> Result<Strong<dyn ICompOsService>> {
         self.0.get_service(COMPOS_VSOCK_PORT).context("Connecting to CompOS service")
     }
 }
 
+fn locate_config_apk(apex_dir: &Path) -> Result<PathBuf> {
+    // Our config APK will be in a directory under app, but the name of the directory is at the
+    // discretion of the build system. So just look in each sub-directory until we find it.
+    // (In practice there will be exactly one directory, so this shouldn't take long.)
+    let app_dir = apex_dir.join("app");
+    for dir in fs::read_dir(app_dir).context("Reading app dir")? {
+        let apk_file = dir?.path().join("CompOSPayloadApp.apk");
+        if apk_file.is_file() {
+            return Ok(apk_file);
+        }
+    }
+
+    bail!("Failed to locate CompOSPayloadApp.apk")
+}
+
 fn prepare_idsig(
     service: &dyn IVirtualizationService,
     apk_fd: &ParcelFileDescriptor,
diff --git a/docs/getting_started/index.md b/docs/getting_started/index.md
index 7eae02d..be97ad5 100644
--- a/docs/getting_started/index.md
+++ b/docs/getting_started/index.md
@@ -102,7 +102,7 @@
 
 ```shell
 mkdir android-kernel && cd android-kernel
-repo init -u https://android.googlesource.com/kernel/manifest -b common-android13-5.15
+repo init -u https://android.googlesource.com/kernel/manifest -b common-android12-5.10
 repo sync
 FAST_BUILD=1 DIST_DIR=out/dist BUILD_CONFIG=common/build.config.gki.aarch64 build/build.sh -j80
 ```
@@ -113,7 +113,7 @@
 Then copy the built kernel to the Android source tree.
 
 ```
-cp out/dist/Image <android_root>/kernel/prebuilts/5.15/arm64/kernel-5.15
+cp out/dist/Image <android_root>/kernel/prebuilts/5.10/arm64/kernel-5.10
 ```
 
 Finally rebuild the `com.android.virt` APEX and install it by following the
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 4b804b1..8702568 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -240,17 +240,17 @@
 ]
 
 bootimg {
-    name: "microdroid_boot",
+    name: "microdroid_boot-5.10",
     // We don't have kernel for arm and x86. But Soong demands one when it builds for
     // arm or x86 target. Satisfy that by providing an empty file as the kernel.
     kernel_prebuilt: "empty_kernel",
     arch: {
         arm64: {
-            kernel_prebuilt: ":kernel_prebuilts-5.15-arm64",
+            kernel_prebuilt: ":kernel_prebuilts-5.10-arm64",
             cmdline: microdroid_boot_cmdline,
         },
         x86_64: {
-            kernel_prebuilt: ":kernel_prebuilts-5.15-x86_64",
+            kernel_prebuilt: ":kernel_prebuilts-5.10-x86_64",
             cmdline: microdroid_boot_cmdline + [
                 // console=none is to work around the x86 specific u-boot behavior which when
                 // console= option is not found in the kernel commandline console=ttyS0 is
@@ -272,7 +272,7 @@
 
 bootimg {
     name: "microdroid_init_boot",
-    ramdisk_module: "microdroid_ramdisk",
+    ramdisk_module: "microdroid_ramdisk-5.10",
     kernel_prebuilt: "empty_kernel",
     header_version: "4",
     partition_name: "init_boot",
@@ -281,7 +281,7 @@
 }
 
 android_filesystem {
-    name: "microdroid_ramdisk",
+    name: "microdroid_ramdisk-5.10",
     deps: [
         "init_first_stage",
     ],
@@ -299,8 +299,8 @@
 }
 
 bootimg {
-    name: "microdroid_vendor_boot",
-    ramdisk_module: "microdroid_vendor_ramdisk",
+    name: "microdroid_vendor_boot-5.10",
+    ramdisk_module: "microdroid_vendor_ramdisk-5.10",
     dtb_prebuilt: "dummy_dtb.img",
     header_version: "4",
     vendor_boot: true,
@@ -321,17 +321,17 @@
     name: "microdroid_kernel_modules",
     arch: {
         arm64: {
-            srcs: [":virt_device_prebuilts_kernel_modules_microdroid-5.15-arm64"],
+            srcs: [":virt_device_prebuilts_kernel_modules_microdroid-5.10-arm64"],
         },
         x86_64: {
-            srcs: [":virt_device_prebuilts_kernel_modules_microdroid-5.15-x86_64"],
+            srcs: [":virt_device_prebuilts_kernel_modules_microdroid-5.10-x86_64"],
         },
     },
-    kernel_version: "5.15",
+    kernel_version: "5.10",
 }
 
 android_filesystem {
-    name: "microdroid_vendor_ramdisk",
+    name: "microdroid_vendor_ramdisk-5.10",
     deps: [
         "microdroid_fstab",
         "microdroid_kernel_modules",
@@ -600,9 +600,9 @@
     private_key: ":microdroid_sign_key",
     partitions: [
         "microdroid_vendor",
-        "microdroid_vendor_boot",
+        "microdroid_vendor_boot-5.10",
         "microdroid",
-        "microdroid_boot",
+        "microdroid_boot-5.10",
         "microdroid_init_boot",
     ],
 }
diff --git a/microdroid/bootconfig.common b/microdroid/bootconfig.common
index eda95a2..362ff23 100644
--- a/microdroid/bootconfig.common
+++ b/microdroid/bootconfig.common
@@ -1,2 +1,3 @@
 androidboot.first_stage_console = 1
 androidboot.hardware = microdroid
+kernel.8250.nr_uarts = 2
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 93cced8..ff3d68e 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -173,7 +173,7 @@
     mkdir /data/local 0751 root root
     mkdir /data/local/tmp 0771 shell shell
 
-service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000
+service tombstone_transmit /system/bin/tombstone_transmit.microdroid -cid 2 -port 2000 -remove_tombstones_after_transmitting
     user root
     group system
 
diff --git a/microdroid/microdroid.json b/microdroid/microdroid.json
index bf8d93e..aff0b7b 100644
--- a/microdroid/microdroid.json
+++ b/microdroid/microdroid.json
@@ -5,7 +5,7 @@
       "partitions": [
         {
           "label": "boot_a",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_boot.img"
+          "path": "/apex/com.android.virt/etc/fs/microdroid_boot-5.10.img"
         },
         {
           "label": "init_boot_a",
@@ -13,7 +13,7 @@
         },
         {
           "label": "vendor_boot_a",
-          "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot.img"
+          "path": "/apex/com.android.virt/etc/fs/microdroid_vendor_boot-5.10.img"
         },
         {
           "label": "vbmeta_a",
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 203e889..8343691 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -24,14 +24,15 @@
         "libidsig",
         "libitertools",
         "libkernlog",
+        "libkeystore2_crypto_rust",
         "liblibc",
         "liblog_rust",
         "libmicrodroid_metadata",
         "libmicrodroid_payload_config",
         "libnix",
         "libonce_cell",
+        "libopenssl",
         "libprotobuf",
-        "libring",
         "librustutils",
         "libserde",
         "libserde_cbor",
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 267a0e3..76c8b23 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -39,8 +39,10 @@
 use anyhow::{anyhow, bail, Context, Result};
 use binder::wait_for_interface;
 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
-use ring::aead::{Aad, Algorithm, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};
-use ring::hkdf::{Salt, HKDF_SHA256};
+use keystore2_crypto::ZVec;
+use openssl::hkdf::hkdf;
+use openssl::md::Md;
+use openssl::symm::{decrypt_aead, encrypt_aead, Cipher};
 use serde::{Deserialize, Serialize};
 use std::fs::{File, OpenOptions};
 use std::io::{Read, Seek, SeekFrom, Write};
@@ -62,8 +64,11 @@
 /// UUID of the partition that microdroid manager uses
 const MICRODROID_PARTITION_UUID: &str = "cf9afe9a-0662-11ec-a329-c32663a09d75";
 
-/// Encryption algorithm used to cipher payload
-static ENCRYPT_ALG: &Algorithm = &AES_256_GCM;
+/// Size of the AES256-GCM tag
+const AES_256_GCM_TAG_LENGTH: usize = 16;
+
+/// Size of the AES256-GCM nonce
+const AES_256_GCM_NONCE_LENGTH: usize = 12;
 
 /// Handle to the instance disk
 pub struct InstanceDisk {
@@ -118,28 +123,31 @@
         let payload_offset = offset + PARTITION_HEADER_SIZE;
         self.file.seek(SeekFrom::Start(payload_offset))?;
 
-        // Read the 12-bytes nonce (unencrypted)
-        let mut nonce = [0; 12];
+        // Read the nonce (unencrypted)
+        let mut nonce = [0; AES_256_GCM_NONCE_LENGTH];
         self.file.read_exact(&mut nonce)?;
-        let nonce = Nonce::assume_unique_for_key(nonce);
 
         // Read the encrypted payload
-        let payload_size = header.payload_size - 12; // we already have read the nonce
-        let mut data = vec![0; payload_size as usize];
+        let payload_size =
+            header.payload_size as usize - AES_256_GCM_NONCE_LENGTH - AES_256_GCM_TAG_LENGTH;
+        let mut data = vec![0; payload_size];
         self.file.read_exact(&mut data)?;
 
+        // Read the tag
+        let mut tag = [0; AES_256_GCM_TAG_LENGTH];
+        self.file.read_exact(&mut tag)?;
+
         // Read the header as well because it's part of the signed data (though not encrypted).
         let mut header = [0; PARTITION_HEADER_SIZE as usize];
         self.file.seek(SeekFrom::Start(offset))?;
         self.file.read_exact(&mut header)?;
 
-        // Decrypt and authenticate the data (along with the header). The data is decrypted in
-        // place. `open_in_place` returns slice to the decrypted part in the buffer.
-        let plaintext_len = get_key()?.open_in_place(nonce, Aad::from(&header), &mut data)?.len();
-        // Truncate to remove the tag
-        data.truncate(plaintext_len);
+        // Decrypt and authenticate the data (along with the header).
+        let key = get_key()?;
+        let plaintext =
+            decrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &tag)?;
 
-        let microdroid_data = serde_cbor::from_slice(data.as_slice())?;
+        let microdroid_data = serde_cbor::from_slice(plaintext.as_slice())?;
         Ok(Some(microdroid_data))
     }
 
@@ -148,12 +156,12 @@
     pub fn write_microdroid_data(&mut self, microdroid_data: &MicrodroidData) -> Result<()> {
         let (header, offset) = self.locate_microdroid_header()?;
 
-        let mut data = serde_cbor::to_vec(microdroid_data)?;
+        let data = serde_cbor::to_vec(microdroid_data)?;
 
         // By encrypting and signing the data, tag will be appended. The tag also becomes part of
-        // the encrypted payload which will be written. In addition, a 12-bytes nonce will be
-        // prepended (non-encrypted).
-        let payload_size = (data.len() + ENCRYPT_ALG.tag_len() + 12) as u64;
+        // the encrypted payload which will be written. In addition, a nonce will be prepended
+        // (non-encrypted).
+        let payload_size = (AES_256_GCM_NONCE_LENGTH + data.len() + AES_256_GCM_TAG_LENGTH) as u64;
 
         // If the partition exists, make sure we don't change the partition size. If not (i.e.
         // partition is not found), write the header at the empty place.
@@ -172,16 +180,19 @@
         self.file.read_exact(&mut header)?;
 
         // Generate a nonce randomly and recorde it on the disk first.
-        let nonce = Nonce::assume_unique_for_key(rand::random::<[u8; 12]>());
+        let nonce = rand::random::<[u8; AES_256_GCM_NONCE_LENGTH]>();
         self.file.seek(SeekFrom::Start(offset + PARTITION_HEADER_SIZE))?;
         self.file.write_all(nonce.as_ref())?;
 
-        // Then encrypt and sign the data. The non-encrypted input data is copied to a vector
-        // because it is encrypted in place, and also the tag is appended.
-        get_key()?.seal_in_place_append_tag(nonce, Aad::from(&header), &mut data)?;
+        // Then encrypt and sign the data.
+        let key = get_key()?;
+        let mut tag = [0; AES_256_GCM_TAG_LENGTH];
+        let ciphertext =
+            encrypt_aead(Cipher::aes_256_gcm(), &key, Some(&nonce), &header, &data, &mut tag)?;
 
-        // Persist the encrypted payload data
-        self.file.write_all(&data)?;
+        // Persist the encrypted payload data and the tag.
+        self.file.write_all(&ciphertext)?;
+        self.file.write_all(&tag)?;
         ioutil::blkflsbuf(&mut self.file)?;
 
         Ok(())
@@ -257,63 +268,21 @@
     Ok(ret)
 }
 
-struct ZeroOnDropKey(LessSafeKey);
-
-impl Drop for ZeroOnDropKey {
-    fn drop(&mut self) {
-        // Zeroize the key by overwriting it with a key constructed from zeros of same length
-        // This works because the raw key bytes are allocated inside the struct, not on the heap
-        let zero = [0; 32];
-        let zero_key = LessSafeKey::new(UnboundKey::new(ENCRYPT_ALG, &zero).unwrap());
-        unsafe {
-            ::std::ptr::write_volatile::<LessSafeKey>(&mut self.0, zero_key);
-        }
-    }
-}
-
-impl std::ops::Deref for ZeroOnDropKey {
-    type Target = LessSafeKey;
-    fn deref(&self) -> &LessSafeKey {
-        &self.0
-    }
-}
-
 /// Returns the key that is used to encrypt the microdroid manager partition. It is derived from
 /// the sealing CDI of the previous stage, which is Android Boot Loader (ABL).
-fn get_key() -> Result<ZeroOnDropKey> {
+fn get_key() -> Result<ZVec> {
     // Sealing CDI from the previous stage.
     let diced = wait_for_interface::<dyn IDiceNode>("android.security.dice.IDiceNode")
         .context("IDiceNode service not found")?;
     let bcc_handover = diced.derive(&[]).context("Failed to get BccHandover")?;
-
-    // Derive a key from the Sealing CDI
-    // Step 1 is extraction: https://datatracker.ietf.org/doc/html/rfc5869#section-2.2 where a
-    // pseduo random key (PRK) is extracted from (Input Keying Material - IKM, which is secret) and
-    // optional salt.
-    let salt = Salt::new(HKDF_SHA256, &[]); // use 0 as salt
-    let prk = salt.extract(&bcc_handover.cdiSeal); // Sealing CDI as IKM
-
-    // Step 2 is expansion: https://datatracker.ietf.org/doc/html/rfc5869#section-2.3 where the PRK
-    // (optionally with the `info` which gives contextual information) is expanded into the output
-    // keying material (OKM). Note that the process fails only when the size of OKM is longer than
-    // 255 * SHA256_HASH_SIZE (32), which isn't the case here.
-    let info = [b"microdroid_manager_key".as_ref()];
-    let okm = prk.expand(&info, HKDF_SHA256).unwrap(); // doesn't fail as explained above
-    let mut key = [0; 32];
-    okm.fill(&mut key).unwrap(); // doesn't fail as explained above
-
-    // The term LessSafe might be misleading here. LessSafe here just means that the API can
-    // possibly accept same nonces for different messages. However, since we encrypt/decrypt only a
-    // single message (the microdroid_manager partition payload) with a randomly generated nonce,
-    // this is safe enough.
-    let ret = ZeroOnDropKey(LessSafeKey::new(UnboundKey::new(ENCRYPT_ALG, &key).unwrap()));
-
-    // Don't forget to zeroize the raw key array as well
-    unsafe {
-        ::std::ptr::write_volatile::<[u8; 32]>(&mut key, [0; 32]);
-    }
-
-    Ok(ret)
+    // Deterministically derive another key to use for encrypting the data, rather than using the
+    // CDI directly, so we have the chance to rotate the key if needed. A salt isn't needed as the
+    // input key material is already cryptographically strong.
+    let salt = &[];
+    let info = b"microdroid_manager_key".as_ref();
+    let mut key = ZVec::new(32)?;
+    hkdf(&mut key, Md::sha256(), &bcc_handover.cdiSeal, salt, info)?;
+    Ok(key)
 }
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index f9b4cf7..929a96b 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -34,9 +34,9 @@
 use log::{error, info};
 use microdroid_metadata::{write_metadata, Metadata};
 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
+use openssl::sha::Sha512;
 use payload::{get_apex_data_from_payload, load_metadata, to_metadata};
 use rand::Fill;
-use ring::digest;
 use rustutils::system_properties;
 use rustutils::system_properties::PropertyWatcher;
 use std::convert::TryInto;
@@ -148,8 +148,8 @@
 
 fn dice_derivation(verified_data: &MicrodroidData, payload_config_path: &str) -> Result<()> {
     // Calculate compound digests of code and authorities
-    let mut code_hash_ctx = digest::Context::new(&digest::SHA512);
-    let mut authority_hash_ctx = digest::Context::new(&digest::SHA512);
+    let mut code_hash_ctx = Sha512::new();
+    let mut authority_hash_ctx = Sha512::new();
     code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
     authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
     for extra_apk in &verified_data.extra_apks_data {
@@ -160,8 +160,8 @@
         code_hash_ctx.update(apex.root_digest.as_ref());
         authority_hash_ctx.update(apex.public_key.as_ref());
     }
-    let code_hash = code_hash_ctx.finish().as_ref().try_into().unwrap();
-    let authority_hash = authority_hash_ctx.finish().as_ref().try_into().unwrap();
+    let code_hash = code_hash_ctx.finish();
+    let authority_hash = authority_hash_ctx.finish();
 
     // {
     //   -70002: "Microdroid payload",
diff --git a/tests/Android.bp b/tests/Android.bp
index a06a33a..2c36a62 100644
--- a/tests/Android.bp
+++ b/tests/Android.bp
@@ -16,7 +16,7 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-kernel_version = "5.15"
+kernel_version = "5.10"
 
 kernel_stem = "kernel_prebuilts-" + kernel_version
 
diff --git a/vmbase/example/Android.bp b/vmbase/example/Android.bp
index 9c19693..0acef2b 100644
--- a/vmbase/example/Android.bp
+++ b/vmbase/example/Android.bp
@@ -61,3 +61,29 @@
         },
     },
 }
+
+rust_test {
+    name: "vmbase_example.integration_test",
+    crate_name: "vmbase_example_test",
+    srcs: ["tests/test.rs"],
+    prefer_rlib: true,
+    edition: "2021",
+    rustlibs: [
+        "android.system.virtualizationservice-rust",
+        "libanyhow",
+        "libenv_logger",
+        "liblibc",
+        "liblog_rust",
+        "libvmclient",
+    ],
+    data: [
+        ":vmbase_example",
+    ],
+    test_suites: ["general-tests"],
+    enabled: false,
+    target: {
+        android_arm64: {
+            enabled: true,
+        },
+    },
+}
diff --git a/vmbase/example/tests/test.rs b/vmbase/example/tests/test.rs
new file mode 100644
index 0000000..4928846
--- /dev/null
+++ b/vmbase/example/tests/test.rs
@@ -0,0 +1,91 @@
+// Copyright 2022, 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.
+
+//! Integration test for VM bootloader.
+
+use android_system_virtualizationservice::{
+    aidl::android::system::virtualizationservice::{
+        VirtualMachineConfig::VirtualMachineConfig,
+        VirtualMachineRawConfig::VirtualMachineRawConfig,
+    },
+    binder::{ParcelFileDescriptor, ProcessState},
+};
+use anyhow::{Context, Error};
+use log::info;
+use std::{
+    fs::File,
+    io,
+    os::unix::io::{AsRawFd, FromRawFd},
+};
+use vmclient::{DeathReason, VmInstance};
+
+const VMBASE_EXAMPLE_PATH: &str =
+    "/data/local/tmp/vmbase_example.integration_test/arm64/vmbase_example.bin";
+
+/// Runs the vmbase_example VM as an unprotected VM via VirtualizationService.
+#[test]
+fn test_run_example_vm() -> Result<(), Error> {
+    env_logger::init();
+
+    // We need to start the thread pool for Binder to work properly, especially link_to_death.
+    ProcessState::start_thread_pool();
+
+    let service = vmclient::connect().context("Failed to find VirtualizationService")?;
+
+    // Start example VM.
+    let bootloader = ParcelFileDescriptor::new(
+        File::open(VMBASE_EXAMPLE_PATH)
+            .with_context(|| format!("Failed to open VM image {}", VMBASE_EXAMPLE_PATH))?,
+    );
+    let config = VirtualMachineConfig::RawConfig(VirtualMachineRawConfig {
+        kernel: None,
+        initrd: None,
+        params: None,
+        bootloader: Some(bootloader),
+        disks: vec![],
+        protectedVm: false,
+        memoryMib: 300,
+        numCpus: 1,
+        cpuAffinity: None,
+        platformVersion: "~1.0".to_string(),
+        taskProfiles: vec![],
+    });
+    let console = duplicate_stdout()?;
+    let log = duplicate_stdout()?;
+    let vm = VmInstance::create(service.as_ref(), &config, Some(console), Some(log))
+        .context("Failed to create VM")?;
+    vm.start().context("Failed to start VM")?;
+    info!("Started example VM.");
+
+    // Wait for VM to finish, and check that it shut down cleanly.
+    let death_reason = vm.wait_for_death();
+    assert_eq!(death_reason, DeathReason::Shutdown);
+
+    Ok(())
+}
+
+/// Safely duplicate the standard output file descriptor.
+fn duplicate_stdout() -> io::Result<File> {
+    let stdout_fd = io::stdout().as_raw_fd();
+    // Safe because this just duplicates a file descriptor which we know to be valid, and we check
+    // for an error.
+    let dup_fd = unsafe { libc::dup(stdout_fd) };
+    if dup_fd < 0 {
+        Err(io::Error::last_os_error())
+    } else {
+        // Safe because we have just duplicated the file descriptor so we own it, and `from_raw_fd`
+        // takes ownership of it.
+        Ok(unsafe { File::from_raw_fd(dup_fd) })
+    }
+}