Merge "Make SkSession mutable" into main
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index a975be0..b21a355 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -108,6 +108,7 @@
         action='store_true',
         help='This will NOT update the vbmeta related bootconfigs while signing the apex.\
             Used for testing only!!')
+    parser.add_argument('--do_not_validate_avb_version', action='store_true', help='Do not validate the avb_version when updating vbmeta bootconfig. Only use in tests!')
     args = parser.parse_args(argv)
     # preprocess --key_override into a map
     args.key_overrides = {}
@@ -328,7 +329,8 @@
             detach_bootconfigs(initrd, tmp_initrd, tmp_bc)
             bc_file = open(tmp_bc, "rt", encoding="utf-8")
             bc_data = bc_file.read()
-            validate_avb_version(bc_data)
+            if not args.do_not_validate_avb_version:
+                validate_avb_version(bc_data)
             bc_data = update_vbmeta_digest(bc_data)
             bc_data = update_vbmeta_size(bc_data)
             bc_file.close()
diff --git a/libs/bssl/src/aead.rs b/libs/bssl/src/aead.rs
index e0c9fbb..1ac2c22 100644
--- a/libs/bssl/src/aead.rs
+++ b/libs/bssl/src/aead.rs
@@ -18,8 +18,8 @@
 use bssl_avf_error::{ApiName, Result};
 use bssl_ffi::{
     EVP_AEAD_CTX_free, EVP_AEAD_CTX_new, EVP_AEAD_CTX_open, EVP_AEAD_CTX_seal,
-    EVP_AEAD_max_overhead, EVP_AEAD_nonce_length, EVP_aead_aes_256_gcm, EVP_AEAD, EVP_AEAD_CTX,
-    EVP_AEAD_DEFAULT_TAG_LENGTH,
+    EVP_AEAD_max_overhead, EVP_AEAD_nonce_length, EVP_aead_aes_256_gcm,
+    EVP_aead_aes_256_gcm_randnonce, EVP_AEAD, EVP_AEAD_CTX, EVP_AEAD_DEFAULT_TAG_LENGTH,
 };
 use core::ptr::NonNull;
 
@@ -51,6 +51,17 @@
         Self(unsafe { &*p })
     }
 
+    /// AES-256 in Galois Counter Mode with internal nonce generation.
+    /// The 12-byte nonce is appended to the tag and is generated internally.
+    pub fn aes_256_gcm_randnonce() -> Self {
+        // SAFETY: This function does not access any Rust variables and simply returns
+        // a pointer to the static variable in BoringSSL.
+        let p = unsafe { EVP_aead_aes_256_gcm_randnonce() };
+        // SAFETY: The returned pointer should always be valid and points to a static
+        // `EVP_AEAD`.
+        Self(unsafe { &*p })
+    }
+
     /// Returns the maximum number of additional bytes added by the act of sealing data.
     pub fn max_overhead(&self) -> usize {
         // SAFETY: This function only reads from self.
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index 9c7eb4f..3c0e45d 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -15,8 +15,8 @@
 use bssl_avf::{sha256, ApiName, Digester, EcKey, EcdsaError, Error, PKey, Result};
 use coset::CborSerializable;
 use spki::{
-    der::{AnyRef, Decode},
-    AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo,
+    der::{AnyRef, Decode, Encode},
+    AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfoRef,
 };
 
 /// OID value for general-use NIST EC keys held in PKCS#8 and X.509; see RFC 5480 s2.1.1.
@@ -46,13 +46,14 @@
     let pkey: PKey = ec_key.try_into()?;
     let subject_public_key_info = pkey.subject_public_key_info()?;
 
-    let subject_public_key_info = SubjectPublicKeyInfo::from_der(&subject_public_key_info).unwrap();
+    let subject_public_key_info =
+        SubjectPublicKeyInfoRef::from_der(&subject_public_key_info).unwrap();
     let expected_algorithm = AlgorithmIdentifier {
         oid: X509_NIST_OID,
         parameters: Some(AnyRef::from(&ALGO_PARAM_P256_OID)),
     };
     assert_eq!(expected_algorithm, subject_public_key_info.algorithm);
-    assert!(!subject_public_key_info.subject_public_key.to_vec().is_empty());
+    assert!(!subject_public_key_info.subject_public_key.to_der().unwrap().is_empty());
     Ok(())
 }
 
diff --git a/microdroid/payload/Android.bp b/microdroid/payload/Android.bp
index 8225875..4814a64 100644
--- a/microdroid/payload/Android.bp
+++ b/microdroid/payload/Android.bp
@@ -31,7 +31,6 @@
     protos: ["metadata.proto"],
     source_stem: "microdroid_metadata",
     host_supported: true,
-    use_protobuf3: true,
     apex_available: [
         "com.android.virt",
     ],
diff --git a/pvmfw/src/crypto.rs b/pvmfw/src/crypto.rs
deleted file mode 100644
index 8f31553..0000000
--- a/pvmfw/src/crypto.rs
+++ /dev/null
@@ -1,271 +0,0 @@
-// Copyright 2023, 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.
-
-//! Wrapper around BoringSSL/OpenSSL symbols.
-
-use core::convert::AsRef;
-use core::ffi::{c_char, c_int, CStr};
-use core::fmt;
-use core::mem::MaybeUninit;
-use core::num::NonZeroU32;
-use core::ptr;
-
-use bssl_ffi::CRYPTO_library_init;
-use bssl_ffi::ERR_get_error_line;
-use bssl_ffi::ERR_lib_error_string;
-use bssl_ffi::ERR_reason_error_string;
-use bssl_ffi::EVP_AEAD_CTX_aead;
-use bssl_ffi::EVP_AEAD_CTX_init;
-use bssl_ffi::EVP_AEAD_CTX_open;
-use bssl_ffi::EVP_AEAD_CTX_seal;
-use bssl_ffi::EVP_AEAD_max_overhead;
-use bssl_ffi::EVP_aead_aes_256_gcm_randnonce;
-use bssl_ffi::EVP_AEAD;
-use bssl_ffi::EVP_AEAD_CTX;
-use cstr::cstr;
-
-#[derive(Debug)]
-pub struct Error {
-    packed: NonZeroU32,
-    file: Option<&'static CStr>,
-    line: c_int,
-}
-
-impl Error {
-    fn get() -> Option<Self> {
-        let mut file = ptr::null();
-        let mut line = 0;
-        // SAFETY: The function writes to the provided pointers, which are valid because they come
-        // from references. It doesn't retain them after it returns.
-        let packed = unsafe { ERR_get_error_line(&mut file, &mut line) };
-
-        let packed = packed.try_into().ok()?;
-        // SAFETY: Any non-NULL result is expected to point to a global const C string.
-        let file = unsafe { as_static_cstr(file) };
-
-        Some(Self { packed, file, line })
-    }
-
-    fn packed_value(&self) -> u32 {
-        self.packed.get()
-    }
-
-    fn library_name(&self) -> Option<&'static CStr> {
-        // SAFETY: Call to a pure function.
-        let name = unsafe { ERR_lib_error_string(self.packed_value()) };
-        // SAFETY: Any non-NULL result is expected to point to a global const C string.
-        unsafe { as_static_cstr(name) }
-    }
-
-    fn reason(&self) -> Option<&'static CStr> {
-        // SAFETY: Call to a pure function.
-        let reason = unsafe { ERR_reason_error_string(self.packed_value()) };
-        // SAFETY: Any non-NULL result is expected to point to a global const C string.
-        unsafe { as_static_cstr(reason) }
-    }
-}
-
-impl fmt::Display for Error {
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let packed = self.packed_value();
-        let library = self.library_name().unwrap_or(cstr!("{unknown library}")).to_str().unwrap();
-        let reason = self.reason().unwrap_or(cstr!("{unknown reason}")).to_str().unwrap();
-        let file = self.file.unwrap_or(cstr!("??")).to_str().unwrap();
-        let line = self.line;
-
-        write!(f, "{file}:{line}: {library}: {reason} ({packed:#x})")
-    }
-}
-
-#[derive(Copy, Clone)]
-pub struct ErrorIterator {}
-
-impl Iterator for ErrorIterator {
-    type Item = Error;
-
-    fn next(&mut self) -> Option<Self::Item> {
-        Self::Item::get()
-    }
-}
-
-pub type Result<T> = core::result::Result<T, ErrorIterator>;
-
-#[repr(transparent)]
-pub struct Aead(EVP_AEAD);
-
-impl Aead {
-    pub fn aes_256_gcm_randnonce() -> Option<&'static Self> {
-        // SAFETY: Returned pointer is checked below.
-        let aead = unsafe { EVP_aead_aes_256_gcm_randnonce() };
-        if aead.is_null() {
-            None
-        } else {
-            // SAFETY: We assume that the non-NULL value points to a valid and static EVP_AEAD.
-            Some(unsafe { &*(aead as *const _) })
-        }
-    }
-
-    pub fn max_overhead(&self) -> usize {
-        // SAFETY: Function should only read from self.
-        unsafe { EVP_AEAD_max_overhead(self.as_ref() as *const _) }
-    }
-}
-
-#[repr(transparent)]
-pub struct AeadCtx(EVP_AEAD_CTX);
-
-impl AeadCtx {
-    pub fn new_aes_256_gcm_randnonce(key: &[u8]) -> Result<Self> {
-        let aead = Aead::aes_256_gcm_randnonce().unwrap();
-
-        Self::new(aead, key)
-    }
-
-    fn new(aead: &'static Aead, key: &[u8]) -> Result<Self> {
-        const DEFAULT_TAG_LENGTH: usize = 0;
-        let engine = ptr::null_mut(); // Use default implementation.
-        let mut ctx = MaybeUninit::zeroed();
-        // SAFETY: Initialize the EVP_AEAD_CTX with const pointers to the AEAD and key.
-        let result = unsafe {
-            EVP_AEAD_CTX_init(
-                ctx.as_mut_ptr(),
-                aead.as_ref() as *const _,
-                key.as_ptr(),
-                key.len(),
-                DEFAULT_TAG_LENGTH,
-                engine,
-            )
-        };
-
-        if result == 1 {
-            // SAFETY: We assume that the non-NULL value points to a valid and static EVP_AEAD.
-            Ok(Self(unsafe { ctx.assume_init() }))
-        } else {
-            Err(ErrorIterator {})
-        }
-    }
-
-    pub fn aead(&self) -> Option<&'static Aead> {
-        // SAFETY: The function should only read from self.
-        let aead = unsafe { EVP_AEAD_CTX_aead(self.as_ref() as *const _) };
-        if aead.is_null() {
-            None
-        } else {
-            // SAFETY: We assume that the non-NULL value points to a valid and static EVP_AEAD.
-            Some(unsafe { &*(aead as *const _) })
-        }
-    }
-
-    pub fn open<'b>(&self, out: &'b mut [u8], data: &[u8]) -> Result<&'b mut [u8]> {
-        let nonce = ptr::null_mut();
-        let nonce_len = 0;
-        let ad = ptr::null_mut();
-        let ad_len = 0;
-        let mut out_len = MaybeUninit::uninit();
-        // SAFETY: The function should only read from self and write to out (at most the provided
-        // number of bytes) and out_len while reading from data (at most the provided number of
-        // bytes), ignoring any NULL input.
-        let result = unsafe {
-            EVP_AEAD_CTX_open(
-                self.as_ref() as *const _,
-                out.as_mut_ptr(),
-                out_len.as_mut_ptr(),
-                out.len(),
-                nonce,
-                nonce_len,
-                data.as_ptr(),
-                data.len(),
-                ad,
-                ad_len,
-            )
-        };
-
-        if result == 1 {
-            // SAFETY: Any value written to out_len could be a valid usize. The value itself is
-            // validated as being a proper slice length by panicking in the following indexing
-            // otherwise.
-            let out_len = unsafe { out_len.assume_init() };
-            Ok(&mut out[..out_len])
-        } else {
-            Err(ErrorIterator {})
-        }
-    }
-
-    pub fn seal<'b>(&self, out: &'b mut [u8], data: &[u8]) -> Result<&'b mut [u8]> {
-        let nonce = ptr::null_mut();
-        let nonce_len = 0;
-        let ad = ptr::null_mut();
-        let ad_len = 0;
-        let mut out_len = MaybeUninit::uninit();
-        // SAFETY: The function should only read from self and write to out (at most the provided
-        // number of bytes) while reading from data (at most the provided number of bytes),
-        // ignoring any NULL input.
-        let result = unsafe {
-            EVP_AEAD_CTX_seal(
-                self.as_ref() as *const _,
-                out.as_mut_ptr(),
-                out_len.as_mut_ptr(),
-                out.len(),
-                nonce,
-                nonce_len,
-                data.as_ptr(),
-                data.len(),
-                ad,
-                ad_len,
-            )
-        };
-
-        if result == 1 {
-            // SAFETY: Any value written to out_len could be a valid usize. The value itself is
-            // validated as being a proper slice length by panicking in the following indexing
-            // otherwise.
-            let out_len = unsafe { out_len.assume_init() };
-            Ok(&mut out[..out_len])
-        } else {
-            Err(ErrorIterator {})
-        }
-    }
-}
-
-/// Cast a C string pointer to a static non-mutable reference.
-///
-/// # Safety
-///
-/// The caller needs to ensure that the pointer is null or points to a valid C string and that the
-/// C lifetime of the string is compatible with a static Rust lifetime.
-unsafe fn as_static_cstr(p: *const c_char) -> Option<&'static CStr> {
-    if p.is_null() {
-        None
-    } else {
-        // Safety: Safe given the requirements of this function.
-        Some(unsafe { CStr::from_ptr(p) })
-    }
-}
-
-impl AsRef<EVP_AEAD> for Aead {
-    fn as_ref(&self) -> &EVP_AEAD {
-        &self.0
-    }
-}
-
-impl AsRef<EVP_AEAD_CTX> for AeadCtx {
-    fn as_ref(&self) -> &EVP_AEAD_CTX {
-        &self.0
-    }
-}
-
-pub fn init() {
-    // SAFETY: Configures the internal state of the library - may be called multiple times.
-    unsafe { CRYPTO_library_init() }
-}
diff --git a/pvmfw/src/device_assignment.rs b/pvmfw/src/device_assignment.rs
index 8d4d840..1b0d8cf 100644
--- a/pvmfw/src/device_assignment.rs
+++ b/pvmfw/src/device_assignment.rs
@@ -687,19 +687,6 @@
         assert_eq!(device_info.assigned_devices, expected);
     }
 
-    // TODO(b/311655051): Test with real once instead of empty FDT.
-    #[test]
-    fn device_info_new_with_empty_device_tree() {
-        let mut fdt_data = vec![0; pvmfw_fdt_template::RAW.len()];
-        let mut vm_dtbo_data = fs::read(VM_DTBO_FILE_PATH).unwrap();
-        let fdt = Fdt::create_empty_tree(&mut fdt_data).unwrap();
-        let vm_dtbo = VmDtbo::from_mut_slice(&mut vm_dtbo_data).unwrap();
-
-        let hypervisor: MockHypervisor = Default::default();
-        let device_info = DeviceAssignmentInfo::parse(fdt, vm_dtbo, &hypervisor).unwrap();
-        assert_eq!(device_info, None);
-    }
-
     #[test]
     fn device_info_filter() {
         let mut fdt_data = fs::read(FDT_FILE_PATH).unwrap();
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 03f2f62..2475f32 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -15,9 +15,9 @@
 //! Low-level entry and exit points of pvmfw.
 
 use crate::config;
-use crate::crypto;
 use crate::fdt;
 use crate::memory;
+use bssl_ffi::CRYPTO_library_init;
 use core::arch::asm;
 use core::mem::{drop, size_of};
 use core::num::NonZeroUsize;
@@ -196,7 +196,12 @@
     // - only access non-pvmfw memory once (and while) it has been mapped
 
     log::set_max_level(LevelFilter::Info);
-    crypto::init();
+    // TODO(https://crbug.com/boringssl/35): Remove this init when BoringSSL can handle this
+    // internally.
+    // SAFETY: Configures the internal state of the library - may be called multiple times.
+    unsafe {
+        CRYPTO_library_init();
+    }
 
     let page_table = memory::init_page_table().map_err(|e| {
         error!("Failed to set up the dynamic page tables: {e}");
diff --git a/pvmfw/src/instance.rs b/pvmfw/src/instance.rs
index a998bfb..e98f663 100644
--- a/pvmfw/src/instance.rs
+++ b/pvmfw/src/instance.rs
@@ -14,13 +14,11 @@
 
 //! Support for reading and writing to the instance.img.
 
-use crate::crypto;
-use crate::crypto::AeadCtx;
 use crate::dice::PartialInputs;
 use crate::gpt;
 use crate::gpt::Partition;
 use crate::gpt::Partitions;
-use bssl_avf::{self, hkdf, Digester};
+use bssl_avf::{self, hkdf, Aead, AeadContext, Digester};
 use core::fmt;
 use core::mem::size_of;
 use diced_open_dice::DiceMode;
@@ -40,12 +38,8 @@
 pub enum Error {
     /// Unexpected I/O error while accessing the underlying disk.
     FailedIo(gpt::Error),
-    /// Failed to decrypt the entry.
-    FailedOpen(crypto::ErrorIterator),
     /// Failed to generate a random salt to be stored.
     FailedSaltGeneration(rand::Error),
-    /// Failed to encrypt the entry.
-    FailedSeal(crypto::ErrorIterator),
     /// Impossible to create a new instance.img entry.
     InstanceImageFull,
     /// Badly formatted instance.img header block.
@@ -72,21 +66,7 @@
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         match self {
             Self::FailedIo(e) => write!(f, "Failed I/O to disk: {e}"),
-            Self::FailedOpen(e_iter) => {
-                writeln!(f, "Failed to open the instance.img partition:")?;
-                for e in *e_iter {
-                    writeln!(f, "\t{e}")?;
-                }
-                Ok(())
-            }
             Self::FailedSaltGeneration(e) => write!(f, "Failed to generate salt: {e}"),
-            Self::FailedSeal(e_iter) => {
-                writeln!(f, "Failed to seal the instance.img partition:")?;
-                for e in *e_iter {
-                    writeln!(f, "\t{e}")?;
-                }
-                Ok(())
-            }
             Self::InstanceImageFull => write!(f, "Failed to obtain a free instance.img partition"),
             Self::InvalidInstanceImageHeader => write!(f, "instance.img header is invalid"),
             Self::MissingInstanceImage => write!(f, "Failed to find the instance.img partition"),
@@ -124,6 +104,13 @@
     trace!("Found pvmfw instance.img entry: {entry:?}");
 
     let key = hkdf::<32>(secret, /* salt= */ &[], b"vm-instance", Digester::sha512())?;
+    let tag_len = None;
+    let aead_ctx = AeadContext::new(Aead::aes_256_gcm_randnonce(), key.as_slice(), tag_len)?;
+    let ad = &[];
+    // The nonce is generated internally for `aes_256_gcm_randnonce`, so no additional
+    // nonce is required.
+    let nonce = &[];
+
     let mut blk = [0; BLK_SIZE];
     match entry {
         PvmfwEntry::Existing { header_index, payload_size } => {
@@ -136,9 +123,7 @@
 
             let payload = &blk[..payload_size];
             let mut entry = [0; size_of::<EntryBody>()];
-            let aead =
-                AeadCtx::new_aes_256_gcm_randnonce(key.as_slice()).map_err(Error::FailedOpen)?;
-            let decrypted = aead.open(&mut entry, payload).map_err(Error::FailedOpen)?;
+            let decrypted = aead_ctx.open(payload, nonce, ad, &mut entry)?;
 
             let body = EntryBody::read_from(decrypted).unwrap();
             if dice_inputs.rkp_vm_marker {
@@ -166,12 +151,10 @@
             let salt = rand::random_array().map_err(Error::FailedSaltGeneration)?;
             let body = EntryBody::new(dice_inputs, &salt);
 
-            let aead =
-                AeadCtx::new_aes_256_gcm_randnonce(key.as_slice()).map_err(Error::FailedSeal)?;
             // We currently only support single-blk entries.
             let plaintext = body.as_bytes();
-            assert!(plaintext.len() + aead.aead().unwrap().max_overhead() < blk.len());
-            let encrypted = aead.seal(&mut blk, plaintext).map_err(Error::FailedSeal)?;
+            assert!(plaintext.len() + aead_ctx.aead().max_overhead() < blk.len());
+            let encrypted = aead_ctx.seal(plaintext, nonce, ad, &mut blk)?;
             let payload_size = encrypted.len();
             let payload_index = header_index + 1;
             instance_img.write_block(payload_index, &blk).map_err(Error::FailedIo)?;
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index 09bb899..f80bae1 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -22,7 +22,6 @@
 mod bcc;
 mod bootargs;
 mod config;
-mod crypto;
 mod device_assignment;
 mod dice;
 mod entry;
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
index 73828a7..91281e7 100644
--- a/service_vm/requests/src/cert.rs
+++ b/service_vm/requests/src/cert.rs
@@ -18,7 +18,7 @@
 use alloc::vec;
 use alloc::vec::Vec;
 use der::{
-    asn1::{BitStringRef, ObjectIdentifier, UIntRef, Utf8StringRef},
+    asn1::{BitString, ObjectIdentifier, OctetString, Utf8StringRef},
     oid::AssociatedOid,
     Decode, Sequence,
 };
@@ -27,6 +27,7 @@
     certificate::{Certificate, TbsCertificate, Version},
     ext::Extension,
     name::Name,
+    serial_number::SerialNumber,
     time::Validity,
 };
 
@@ -111,14 +112,14 @@
 ///   signature            BIT STRING
 /// }
 /// ```
-pub(crate) fn build_certificate<'a>(
-    tbs_cert: TbsCertificate<'a>,
-    signature: &'a [u8],
-) -> der::Result<Certificate<'a>> {
+pub(crate) fn build_certificate(
+    tbs_cert: TbsCertificate,
+    signature: &[u8],
+) -> der::Result<Certificate> {
     Ok(Certificate {
-        signature_algorithm: tbs_cert.signature,
+        signature_algorithm: tbs_cert.signature.clone(),
         tbs_certificate: tbs_cert,
-        signature: BitStringRef::new(0, signature)?,
+        signature: BitString::new(0, signature)?,
     })
 }
 
@@ -141,24 +142,24 @@
 ///                        -- If present, version MUST be v3 --
 /// }
 /// ```
-pub(crate) fn build_tbs_certificate<'a>(
-    serial_number: &'a [u8],
-    issuer: Name<'a>,
-    subject: Name<'a>,
+pub(crate) fn build_tbs_certificate(
+    serial_number: &[u8],
+    issuer: Name,
+    subject: Name,
     validity: Validity,
-    subject_public_key_info: &'a [u8],
-    attestation_ext: &'a [u8],
-) -> der::Result<TbsCertificate<'a>> {
+    subject_public_key_info: &[u8],
+    attestation_ext: &[u8],
+) -> der::Result<TbsCertificate> {
     let signature = AlgorithmIdentifier { oid: ECDSA_WITH_SHA_256, parameters: None };
     let subject_public_key_info = SubjectPublicKeyInfo::from_der(subject_public_key_info)?;
     let extensions = vec![Extension {
         extn_id: AttestationExtension::OID,
         critical: false,
-        extn_value: attestation_ext,
+        extn_value: OctetString::new(attestation_ext)?,
     }];
     Ok(TbsCertificate {
         version: Version::V3,
-        serial_number: UIntRef::new(serial_number)?,
+        serial_number: SerialNumber::new(serial_number)?,
         signature,
         issuer,
         validity,
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index c2f39e7..5b1bf6c 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -103,7 +103,7 @@
         client_vm_dice_chain.all_entries_are_secure(),
         vm_components,
     )
-    .to_vec()?;
+    .to_der()?;
     let tbs_cert = cert::build_tbs_certificate(
         &serial_number,
         rkp_cert.tbs_certificate.subject,
@@ -122,9 +122,9 @@
                 RequestProcessingError::FailedToDecryptKeyBlob
             })?;
     let ec_private_key = EcKey::from_ec_private_key(private_key.as_slice())?;
-    let signature = ecdsa_sign(&ec_private_key, &tbs_cert.to_vec()?)?;
+    let signature = ecdsa_sign(&ec_private_key, &tbs_cert.to_der()?)?;
     let certificate = cert::build_certificate(tbs_cert, &signature)?;
-    Ok(certificate.to_vec()?)
+    Ok(certificate.to_der()?)
 }
 
 fn ecdsa_verify(key: &EcKey, signature: &[u8], message: &[u8]) -> bssl_avf::Result<()> {
diff --git a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
index 937fbee..be13196 100644
--- a/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
+++ b/tests/hostside/helper/java/com/android/microdroid/test/host/MicrodroidHostTestCaseBase.java
@@ -36,6 +36,8 @@
 import java.io.File;
 import java.io.FileNotFoundException;
 import java.util.Arrays;
+import java.util.HashSet;
+import java.util.Set;
 
 public abstract class MicrodroidHostTestCaseBase extends BaseHostJUnit4Test {
     protected static final String TEST_ROOT = "/data/local/tmp/virt/";
@@ -52,6 +54,9 @@
             (int) (MICRODROID_ADB_CONNECT_TIMEOUT_MINUTES * 60 * 1000
                 / MICRODROID_COMMAND_RETRY_INTERVAL_MILLIS);
 
+    protected static final Set<String> SUPPORTED_GKI_VERSIONS =
+            new HashSet(Arrays.asList("android14-6.1"));
+
     public static void prepareVirtualizationTestSetup(ITestDevice androidDevice)
             throws DeviceNotAvailableException {
         CommandRunner android = new CommandRunner(androidDevice);
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 60f3e52..a54a22a 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -25,6 +25,7 @@
 
 import static org.hamcrest.CoreMatchers.containsString;
 import static org.junit.Assert.assertThat;
+import static org.junit.Assert.assertTrue;
 import static org.junit.Assume.assumeFalse;
 import static org.junit.Assume.assumeTrue;
 
@@ -102,14 +103,24 @@
         }
     }
 
-    @Parameterized.Parameters(name = "protectedVm={0}")
+    @Parameterized.Parameters(name = "protectedVm={0},gki={1}")
     public static Collection<Object[]> params() {
-        return List.of(new Object[] {true}, new Object[] {false});
+        List<Object[]> ret = new ArrayList<>();
+        ret.add(new Object[] {true /* protectedVm */, null /* use microdroid kernel */});
+        ret.add(new Object[] {false /* protectedVm */, null /* use microdroid kernel */});
+        for (String gki : SUPPORTED_GKI_VERSIONS) {
+            ret.add(new Object[] {true /* protectedVm */, gki});
+            ret.add(new Object[] {false /* protectedVm */, gki});
+        }
+        return ret;
     }
 
     @Parameterized.Parameter(0)
     public boolean mProtectedVm;
 
+    @Parameterized.Parameter(1)
+    public String mGki;
+
     @Rule public TestLogData mTestLogs = new TestLogData();
     @Rule public TestName mTestName = new TestName();
     @Rule public TestMetrics mMetrics = new TestMetrics();
@@ -164,6 +175,12 @@
         if (!updateBootconfigs) {
             command.add("--do_not_update_bootconfigs");
         }
+        // In some cases we run a CTS binary that is built from a different branch that the /system
+        // image under test. In such cases we might end up in a situation when avb_version used in
+        // CTS binary and avb_version used to sign the com.android.virt APEX do not match.
+        // This is a weird configuration, but unfortunately it can happen, hence we pass here
+        // --do_not_validate_avb_version flag to make sure that CTS doesn't fail on it.
+        command.add("--do_not_validate_avb_version");
         keyOverrides.forEach(
                 (filename, keyFile) ->
                         command.add("--key_override " + filename + "=" + keyFile.getPath()));
@@ -316,7 +333,8 @@
         //   - its idsig
 
         // Load etc/microdroid.json
-        File microdroidConfigFile = new File(virtApexEtcDir, "microdroid.json");
+        String os = mGki != null ? "microdroid_gki-" + mGki : "microdroid";
+        File microdroidConfigFile = new File(virtApexEtcDir, os + ".json");
         JSONObject config = new JSONObject(FileUtil.readStringFromFile(microdroidConfigFile));
 
         // Replace paths so that the config uses re-signed images from TEST_ROOT
@@ -332,7 +350,7 @@
         }
 
         // Add partitions to the second disk
-        final String initrdPath = TEST_ROOT + "etc/microdroid_initrd_debuggable.img";
+        final String initrdPath = TEST_ROOT + "etc/" + os + "_initrd_debuggable.img";
         config.put("initrd", initrdPath);
         // Add instance image as a partition in disks[1]
         disks.put(
@@ -400,6 +418,7 @@
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(true)
+                        .gki(mGki)
                         .build(getAndroidDevice());
 
         // Assert
@@ -526,6 +545,7 @@
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(protectedVm)
+                        .gki(mGki)
                         .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
@@ -680,6 +700,7 @@
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(mProtectedVm)
+                        .gki(mGki)
                         .build(device);
         microdroid.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         device.shutdownMicrodroid(microdroid);
@@ -808,24 +829,8 @@
                         .debugLevel("full")
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
-                        .protectedVm(mProtectedVm));
-    }
-
-    @Test
-    @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
-    public void testMicrodroidBootsWithGki() throws Exception {
-        List<String> supportedVersions = getSupportedGKIVersions();
-        assumeFalse("no available gki", supportedVersions.isEmpty());
-        for (String ver : supportedVersions) {
-            final String configPath = "assets/vm_config.json"; // path inside the APK
-            testMicrodroidBootsWithBuilder(
-                    MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
-                            .debugLevel("full")
-                            .memoryMib(minMemorySize())
-                            .cpuTopology("match_host")
-                            .protectedVm(mProtectedVm)
-                            .gki(ver));
-        }
+                        .protectedVm(mProtectedVm)
+                        .gki(mGki));
     }
 
     @Test
@@ -837,6 +842,7 @@
                         .memoryMib(minMemorySize())
                         .cpuTopology("match_host")
                         .protectedVm(mProtectedVm)
+                        .gki(mGki)
                         .build(getAndroidDevice());
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
         mMicrodroidDevice.enableAdbRoot();
@@ -992,11 +998,21 @@
                         .cpuTopology("match_host")
                         .protectedVm(true)
                         .addAssignableDevice(devices.get(0))
+                        .gki(mGki)
                         .build(getAndroidDevice());
 
         mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
     }
 
+    @Test
+    public void testGkiVersions() throws Exception {
+        for (String gki : getSupportedGKIVersions()) {
+            assertTrue(
+                    "Unknown gki \"" + gki + "\". Supported gkis: " + SUPPORTED_GKI_VERSIONS,
+                    SUPPORTED_GKI_VERSIONS.contains(gki));
+        }
+    }
+
     @Before
     public void setUp() throws Exception {
         assumeDeviceIsCapable(getDevice());
@@ -1011,6 +1027,12 @@
         assumeTrue(
                 "Microdroid is not supported for specific VM protection type",
                 getAndroidDevice().supportsMicrodroid(mProtectedVm));
+
+        if (mGki != null) {
+            assumeTrue(
+                    "GKI version \"" + mGki + "\" is not supported on this device",
+                    getSupportedGKIVersions().contains(mGki));
+        }
     }
 
     @After
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 2ba0e0e..f0c3e4b 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -900,7 +900,9 @@
         .arg("--socket")
         .arg(add_preserved_fd(&mut preserved_fds, &control_server_socket.as_raw_descriptor()));
 
-    // TODO(b/285855436): Pass dtbo_vendor after --device-tree-overlay crosvm option is supported.
+    if let Some(dtbo_vendor) = &config.dtbo_vendor {
+        command.arg("--device-tree-overlay").arg(add_preserved_fd(&mut preserved_fds, dtbo_vendor));
+    }
 
     append_platform_devices(&mut command, &mut preserved_fds, &config)?;