Merge "[bssl] Check the public EC_KEY built from COSE_Key" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index a9193d7..4da96c8 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -34,6 +34,9 @@
},
{
"name": "libapkzip.test"
+ },
+ {
+ "name": "libsecretkeeper_comm.test"
}
],
"avf-postsubmit": [
@@ -114,6 +117,9 @@
"path": "packages/modules/Virtualization/service_vm/requests"
},
{
+ "path": "packages/modules/Virtualization/virtualizationservice"
+ },
+ {
"path": "packages/modules/Virtualization/vm"
},
{
diff --git a/apex/Android.bp b/apex/Android.bp
index f2a0d64..acbc0a1 100644
--- a/apex/Android.bp
+++ b/apex/Android.bp
@@ -73,11 +73,13 @@
config_namespace: "ANDROID",
bool_variables: [
"release_avf_enable_device_assignment",
+ "release_avf_enable_remote_attestation",
"release_avf_enable_vendor_modules",
],
properties: [
"arch",
"prebuilts",
+ "vintf_fragments",
],
}
@@ -112,7 +114,6 @@
"vm",
],
prebuilts: [
- "com.android.virt.init.rc",
"features_com.android.virt.xml",
"microdroid_initrd_debuggable",
"microdroid_initrd_normal",
@@ -142,12 +143,21 @@
},
release_avf_enable_vendor_modules: {
prebuilts: [
- "microdroid_gki_initrd_debuggable",
- "microdroid_gki_initrd_normal",
- "microdroid_gki_kernel",
- "microdroid_gki.json",
+ "microdroid_gki-6.1_initrd_debuggable",
+ "microdroid_gki-6.1_initrd_normal",
+ "microdroid_gki-6.1_kernel",
+ "microdroid_gki-6.1.json",
],
},
+ release_avf_enable_remote_attestation: {
+ prebuilts: ["com.android.virt.init_attestation_enabled.rc"],
+ vintf_fragments: [
+ "virtualizationservice.xml",
+ ],
+ conditions_default: {
+ prebuilts: ["com.android.virt.init.rc"],
+ },
+ },
},
}
@@ -171,7 +181,14 @@
prebuilt_etc {
name: "com.android.virt.init.rc",
src: "virtualizationservice.rc",
- filename: "init.rc",
+ filename: "virtualizationservice.rc",
+ installable: false,
+}
+
+prebuilt_etc {
+ name: "com.android.virt.init_attestation_enabled.rc",
+ src: "virtualizationservice_attestation_enabled.rc",
+ filename: "virtualizationservice.rc",
installable: false,
}
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 7393636..0c5bc72 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -410,21 +410,35 @@
'--output_vbmeta_image', output]
RunCommand(args, cmd)
+
+gki_versions = ['6.1']
+
# dict of (key, file) for re-sign/verification. keys are un-versioned for readability.
-virt_apex_files = {
+virt_apex_non_gki_files = {
'kernel': 'etc/fs/microdroid_kernel',
- 'gki_kernel': 'etc/fs/microdroid_gki_kernel',
'vbmeta.img': 'etc/fs/microdroid_vbmeta.img',
'super.img': 'etc/fs/microdroid_super.img',
'initrd_normal.img': 'etc/microdroid_initrd_normal.img',
- 'gki_initrd_normal.img': 'etc/microdroid_gki_initrd_normal.img',
'initrd_debuggable.img': 'etc/microdroid_initrd_debuggable.img',
- 'gki_initrd_debuggable.img': 'etc/microdroid_gki_initrd_debuggable.img',
}
-
def TargetFiles(input_dir):
- return {k: os.path.join(input_dir, v) for k, v in virt_apex_files.items()}
+ ret = {k: os.path.join(input_dir, v) for k, v in virt_apex_non_gki_files.items()}
+
+ for ver in gki_versions:
+ kernel = os.path.join(input_dir, f'etc/fs/microdroid_gki-{ver}_kernel')
+ initrd_normal = os.path.join(input_dir, f'etc/microdroid_gki-{ver}_initrd_normal.img')
+ initrd_debug = os.path.join(input_dir, f'etc/microdroid_gki-{ver}_initrd_debuggable.img')
+
+ if os.path.isfile(kernel):
+ ret[f'gki-{ver}_kernel'] = kernel
+ ret[f'gki-{ver}_initrd_normal.img'] = initrd_normal
+ ret[f'gki-{ver}_initrd_debuggable.img'] = initrd_debug
+
+ return ret
+
+def IsInitrdImage(path):
+ return path.endswith('initrd_normal.img') or path.endswith('initrd_debuggable.img')
def SignVirtApex(args):
@@ -461,13 +475,9 @@
images=images,
wait=images_f)
- has_gki_kernel = os.path.isfile(files['gki_kernel'])
-
vbmeta_bc_f = None
if not args.do_not_update_bootconfigs:
- initrd_files = [files['initrd_normal.img'], files['initrd_debuggable.img']]
- if has_gki_kernel:
- initrd_files += [files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']]
+ initrd_files = [v for k, v in files.items() if IsInitrdImage(k)]
vbmeta_bc_f = Async(UpdateVbmetaBootconfig, args, initrd_files,
files['vbmeta.img'],
wait=[vbmeta_f])
@@ -493,8 +503,12 @@
resign_kernel('kernel', 'initrd_normal.img', 'initrd_debuggable.img')
- if has_gki_kernel:
- resign_kernel('gki_kernel', 'gki_initrd_normal.img', 'gki_initrd_debuggable.img')
+ for ver in gki_versions:
+ if f'gki-{ver}_kernel' in files:
+ resign_kernel(
+ f'gki-{ver}_kernel',
+ f'gki-{ver}_initrd_normal.img',
+ f'gki-{ver}_initrd_debuggable.img')
def VerifyVirtApex(args):
@@ -518,12 +532,11 @@
assert info is not None, f'no avbinfo: {file}'
assert info['Public key (sha1)'] == pubkey_digest, f'pubkey mismatch: {file}'
- for f in files.values():
- if f in (files['initrd_normal.img'], files['initrd_debuggable.img'],
- files['gki_initrd_normal.img'], files['gki_initrd_debuggable.img']):
+ for k, f in files.items():
+ if IsInitrdImage(k):
# TODO(b/245277660): Verify that ramdisks contain the correct vbmeta digest
continue
- if f == files['super.img']:
+ if k == 'super.img':
Async(check_avb_pubkey, system_a_img)
else:
# Check pubkey for other files using avbtool
diff --git a/apex/virtualizationservice.xml b/apex/virtualizationservice.xml
index 0ce1e10..60f466f 100644
--- a/apex/virtualizationservice.xml
+++ b/apex/virtualizationservice.xml
@@ -1,6 +1,6 @@
<manifest version="1.0" type="framework">
<hal format="aidl">
- <name>android.system.virtualization</name>
+ <name>android.hardware.security.keymint</name>
<version>3</version>
<fqname>IRemotelyProvisionedComponent/avf</fqname>
</hal>
diff --git a/apex/virtualizationservice_attestation_enabled.rc b/apex/virtualizationservice_attestation_enabled.rc
new file mode 100644
index 0000000..8eaccae
--- /dev/null
+++ b/apex/virtualizationservice_attestation_enabled.rc
@@ -0,0 +1,22 @@
+# 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.
+
+service virtualizationservice /apex/com.android.virt/bin/virtualizationservice
+ class main
+ user system
+ group system
+ interface aidl android.system.virtualizationservice
+ interface aidl android.hardware.security.keymint.IRemotelyProvisionedComponent/avf
+ disabled
+ oneshot
diff --git a/libs/apexutil/Android.bp b/libs/apexutil/Android.bp
index 92d4e80..f9b72c4 100644
--- a/libs/apexutil/Android.bp
+++ b/libs/apexutil/Android.bp
@@ -6,7 +6,6 @@
name: "libapexutil_rust.defaults",
crate_name: "apexutil",
defaults: ["avf_build_flags_rust"],
- host_supported: true,
srcs: ["src/lib.rs"],
edition: "2021",
rustlibs: [
@@ -31,14 +30,4 @@
rustlibs: [
"libhex",
],
- target: {
- host: {
- // TODO(b/204562227): remove once the build does this automatically
- data_libs: [
- "libc++",
- "libcrypto",
- "libz",
- ],
- },
- },
}
diff --git a/libs/bssl/Android.bp b/libs/bssl/Android.bp
index ff45af9..e1f4ffd 100644
--- a/libs/bssl/Android.bp
+++ b/libs/bssl/Android.bp
@@ -46,5 +46,6 @@
rustlibs: [
"libbssl_avf_nostd",
"libcoset_nostd",
+ "libspki_nostd",
],
}
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 89865d4..c0dca2e 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -81,6 +81,9 @@
EVP_AEAD_CTX_new,
EVP_AEAD_CTX_open,
EVP_AEAD_CTX_seal,
+ EVP_PKEY_new,
+ EVP_PKEY_set1_EC_KEY,
+ EVP_marshal_public_key,
HKDF,
HMAC,
RAND_bytes,
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 5a22e65..a187259 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -45,7 +45,7 @@
type Coordinate = [u8; P256_AFFINE_COORDINATE_SIZE];
/// Wrapper of an `EC_KEY` object, representing a public or private EC key.
-pub struct EcKey(NonNull<EC_KEY>);
+pub struct EcKey(pub(crate) NonNull<EC_KEY>);
impl Drop for EcKey {
fn drop(&mut self) {
diff --git a/libs/bssl/src/evp.rs b/libs/bssl/src/evp.rs
new file mode 100644
index 0000000..30bfc21
--- /dev/null
+++ b/libs/bssl/src/evp.rs
@@ -0,0 +1,90 @@
+// 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.
+
+//! Wrappers of the EVP functions in BoringSSL evp.h.
+
+use crate::cbb::CbbFixed;
+use crate::ec_key::EcKey;
+use crate::util::{check_int_result, to_call_failed_error};
+use alloc::vec::Vec;
+use bssl_avf_error::{ApiName, Result};
+use bssl_ffi::{
+ CBB_flush, CBB_len, EVP_PKEY_free, EVP_PKEY_new, EVP_PKEY_set1_EC_KEY, EVP_marshal_public_key,
+ EVP_PKEY,
+};
+use core::ptr::NonNull;
+
+/// Wrapper of an `EVP_PKEY` object, representing a public or private key.
+pub struct EvpPKey {
+ pkey: NonNull<EVP_PKEY>,
+ /// Since this struct owns the inner key, the inner key remains valid as
+ /// long as the pointer to `EVP_PKEY` is valid.
+ _inner_key: EcKey,
+}
+
+impl Drop for EvpPKey {
+ fn drop(&mut self) {
+ // SAFETY: It is safe because `EVP_PKEY` has been allocated by BoringSSL and isn't
+ // used after this.
+ unsafe { EVP_PKEY_free(self.pkey.as_ptr()) }
+ }
+}
+
+/// Creates a new empty `EVP_PKEY`.
+fn new_pkey() -> Result<NonNull<EVP_PKEY>> {
+ // SAFETY: The returned pointer is checked below.
+ let key = unsafe { EVP_PKEY_new() };
+ NonNull::new(key).ok_or(to_call_failed_error(ApiName::EVP_PKEY_new))
+}
+
+impl TryFrom<EcKey> for EvpPKey {
+ type Error = bssl_avf_error::Error;
+
+ fn try_from(key: EcKey) -> Result<Self> {
+ let pkey = new_pkey()?;
+ // SAFETY: The function only sets the inner key of the initialized and
+ // non-null `EVP_PKEY` to point to the given `EC_KEY`. It only reads from
+ // and writes to the initialized `EVP_PKEY`.
+ // Since this struct owns the inner key, the inner key remains valid as
+ // long as `EVP_PKEY` is valid.
+ let ret = unsafe { EVP_PKEY_set1_EC_KEY(pkey.as_ptr(), key.0.as_ptr()) };
+ check_int_result(ret, ApiName::EVP_PKEY_set1_EC_KEY)?;
+ Ok(Self { pkey, _inner_key: key })
+ }
+}
+
+impl EvpPKey {
+ /// Returns a DER-encoded SubjectPublicKeyInfo structure as specified
+ /// in RFC 5280 s4.1.2.7:
+ ///
+ /// https://www.rfc-editor.org/rfc/rfc5280.html#section-4.1.2.7
+ pub fn subject_public_key_info(&self) -> Result<Vec<u8>> {
+ const CAPACITY: usize = 256;
+ let mut buf = [0u8; CAPACITY];
+ let mut cbb = CbbFixed::new(buf.as_mut());
+ // SAFETY: The function only write bytes to the buffer managed by the valid `CBB`.
+ // The inner key in `EVP_PKEY` was set to a valid key when the object was created.
+ // As this struct owns the inner key, the inner key is guaranteed to be valid
+ // throughout the execution of the function.
+ let ret = unsafe { EVP_marshal_public_key(cbb.as_mut(), self.pkey.as_ptr()) };
+ check_int_result(ret, ApiName::EVP_marshal_public_key)?;
+ // SAFETY: This is safe because the CBB pointer is a valid pointer initialized with
+ // `CBB_init_fixed()`.
+ check_int_result(unsafe { CBB_flush(cbb.as_mut()) }, ApiName::CBB_flush)?;
+ // SAFETY: This is safe because the CBB pointer is initialized with `CBB_init_fixed()`,
+ // and it has been flushed, thus it has no active children.
+ let len = unsafe { CBB_len(cbb.as_ref()) };
+ Ok(buf.get(0..len).ok_or(to_call_failed_error(ApiName::CBB_len))?.to_vec())
+ }
+}
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 8e3abcf..e378386 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -24,6 +24,7 @@
mod digest;
mod ec_key;
mod err;
+mod evp;
mod hkdf;
mod hmac;
mod rand;
@@ -37,6 +38,7 @@
pub use cbs::Cbs;
pub use digest::Digester;
pub use ec_key::{EcKey, ZVec};
+pub use evp::EvpPKey;
pub use hkdf::hkdf;
pub use hmac::hmac_sha256;
pub use rand::rand_bytes;
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
index 3dd243c..968af63 100644
--- a/libs/bssl/tests/eckey_test.rs
+++ b/libs/bssl/tests/eckey_test.rs
@@ -12,8 +12,18 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, Result};
+use bssl_avf::{sha256, ApiName, EcKey, EcdsaError, Error, EvpPKey, Result};
use coset::CborSerializable;
+use spki::{
+ der::{AnyRef, Decode},
+ AlgorithmIdentifier, ObjectIdentifier, SubjectPublicKeyInfo,
+};
+
+/// OID value for general-use NIST EC keys held in PKCS#8 and X.509; see RFC 5480 s2.1.1.
+const X509_NIST_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.2.1");
+
+/// OID value in `AlgorithmIdentifier.parameters` for P-256; see RFC 5480 s2.1.1.1.
+const ALGO_PARAM_P256_OID: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.3.1.7");
const MESSAGE1: &[u8] = b"test message 1";
const MESSAGE2: &[u8] = b"test message 2";
@@ -30,6 +40,23 @@
}
#[test]
+fn subject_public_key_info_serialization() -> Result<()> {
+ let mut ec_key = EcKey::new_p256()?;
+ ec_key.generate_key()?;
+ let pkey: EvpPKey = 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 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());
+ Ok(())
+}
+
+#[test]
fn cose_public_key_serialization() -> Result<()> {
let mut ec_key = EcKey::new_p256()?;
ec_key.generate_key()?;
diff --git a/libs/dice/open_dice/src/bcc.rs b/libs/dice/open_dice/src/bcc.rs
index 199e1a9..9c9545b 100644
--- a/libs/dice/open_dice/src/bcc.rs
+++ b/libs/dice/open_dice/src/bcc.rs
@@ -20,7 +20,7 @@
DiceAndroidConfigValues, DiceAndroidFormatConfigDescriptor, DiceAndroidHandoverMainFlow,
DiceAndroidHandoverParse, DiceAndroidMainFlow, DICE_ANDROID_CONFIG_COMPONENT_NAME,
DICE_ANDROID_CONFIG_COMPONENT_VERSION, DICE_ANDROID_CONFIG_RESETTABLE,
- DICE_ANDROID_CONFIG_SECURITY_VERSION,
+ DICE_ANDROID_CONFIG_RKP_VM_MARKER, DICE_ANDROID_CONFIG_SECURITY_VERSION,
};
use std::{ffi::CStr, ptr};
@@ -36,6 +36,8 @@
pub resettable: bool,
/// Monotonically increasing version of the component.
pub security_version: Option<u64>,
+ /// Whether the component can take part in running the RKP VM.
+ pub rkp_vm_marker: bool,
}
/// Formats a configuration descriptor following the Android Profile for DICE specification.
@@ -58,6 +60,9 @@
configs |= DICE_ANDROID_CONFIG_SECURITY_VERSION;
version
});
+ if values.rkp_vm_marker {
+ configs |= DICE_ANDROID_CONFIG_RKP_VM_MARKER;
+ }
let values =
DiceAndroidConfigValues { configs, component_name, component_version, security_version };
diff --git a/libs/hyp/src/hypervisor.rs b/libs/hyp/src/hypervisor.rs
index 309f967..3d42ccb 100644
--- a/libs/hyp/src/hypervisor.rs
+++ b/libs/hyp/src/hypervisor.rs
@@ -34,6 +34,8 @@
use smccc::hvc64;
use uuid::Uuid;
+use self::common::DeviceAssigningHypervisor;
+
enum HypervisorBackend {
RegularKvm,
Gunyah,
@@ -122,3 +124,8 @@
pub fn get_mem_sharer() -> Option<&'static dyn MemSharingHypervisor> {
get_hypervisor().as_mem_sharer()
}
+
+/// Gets the device assigning hypervisor singleton, if any.
+pub fn get_device_assigner() -> Option<&'static dyn DeviceAssigningHypervisor> {
+ get_hypervisor().as_device_assigner()
+}
diff --git a/libs/hyp/src/hypervisor/common.rs b/libs/hyp/src/hypervisor/common.rs
index 70fdd0a..2ea18f3 100644
--- a/libs/hyp/src/hypervisor/common.rs
+++ b/libs/hyp/src/hypervisor/common.rs
@@ -31,6 +31,11 @@
fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
None
}
+
+ /// Returns the hypervisor's device assigning implementation, if any.
+ fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+ None
+ }
}
pub trait MmioGuardedHypervisor {
@@ -73,3 +78,11 @@
/// Returns the memory protection granule size in bytes.
fn granule(&self) -> Result<usize>;
}
+
+pub trait DeviceAssigningHypervisor {
+ /// Returns MMIO token.
+ fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64>;
+
+ /// Returns DMA token as a tuple of (phys_iommu_id, phys_sid).
+ fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)>;
+}
diff --git a/libs/hyp/src/hypervisor/kvm.rs b/libs/hyp/src/hypervisor/kvm.rs
index 5835346..720318e 100644
--- a/libs/hyp/src/hypervisor/kvm.rs
+++ b/libs/hyp/src/hypervisor/kvm.rs
@@ -14,7 +14,9 @@
//! Wrappers around calls to the KVM hypervisor.
-use super::common::{Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor};
+use super::common::{
+ DeviceAssigningHypervisor, Hypervisor, MemSharingHypervisor, MmioGuardedHypervisor,
+};
use crate::error::{Error, Result};
use crate::util::page_address;
use core::fmt::{self, Display, Formatter};
@@ -70,6 +72,9 @@
const VENDOR_HYP_KVM_MMIO_GUARD_MAP_FUNC_ID: u32 = 0xc6000007;
const VENDOR_HYP_KVM_MMIO_GUARD_UNMAP_FUNC_ID: u32 = 0xc6000008;
+const VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID: u32 = 0xc6000012;
+const VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID: u32 = 0xc6000013;
+
pub(super) struct RegularKvmHypervisor;
impl RegularKvmHypervisor {
@@ -90,6 +95,10 @@
fn as_mem_sharer(&self) -> Option<&dyn MemSharingHypervisor> {
Some(self)
}
+
+ fn as_device_assigner(&self) -> Option<&dyn DeviceAssigningHypervisor> {
+ Some(self)
+ }
}
impl MmioGuardedHypervisor for ProtectedKvmHypervisor {
@@ -153,6 +162,26 @@
}
}
+impl DeviceAssigningHypervisor for ProtectedKvmHypervisor {
+ fn get_phys_mmio_token(&self, base_ipa: u64, size: u64) -> Result<u64> {
+ let mut args = [0u64; 17];
+ args[0] = base_ipa;
+ args[1] = size;
+
+ let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_MMIO_FUNC_ID, args)?;
+ Ok(ret[0])
+ }
+
+ fn get_phys_iommu_token(&self, pviommu_id: u64, vsid: u64) -> Result<(u64, u64)> {
+ let mut args = [0u64; 17];
+ args[0] = pviommu_id;
+ args[1] = vsid;
+
+ let ret = checked_hvc64_expect_results(VENDOR_HYP_KVM_DEV_REQ_DMA_FUNC_ID, args)?;
+ Ok((ret[0], ret[1]))
+ }
+}
+
fn checked_hvc64_expect_zero(function: u32, args: [u64; 17]) -> Result<()> {
success_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
}
@@ -160,3 +189,9 @@
fn checked_hvc64(function: u32, args: [u64; 17]) -> Result<u64> {
positive_or_error_64(hvc64(function, args)[0]).map_err(|e| Error::KvmError(e, function))
}
+
+fn checked_hvc64_expect_results(function: u32, args: [u64; 17]) -> Result<[u64; 17]> {
+ let [ret, results @ ..] = hvc64(function, args);
+ success_or_error_64(ret).map_err(|e| Error::KvmError(e, function))?;
+ Ok(results)
+}
diff --git a/libs/hyp/src/lib.rs b/libs/hyp/src/lib.rs
index 486a181..505aade 100644
--- a/libs/hyp/src/lib.rs
+++ b/libs/hyp/src/lib.rs
@@ -21,6 +21,8 @@
mod util;
pub use error::{Error, Result};
-pub use hypervisor::{get_mem_sharer, get_mmio_guard, KvmError, MMIO_GUARD_GRANULE_SIZE};
+pub use hypervisor::{
+ get_device_assigner, get_mem_sharer, get_mmio_guard, KvmError, MMIO_GUARD_GRANULE_SIZE,
+};
use hypervisor::GeniezoneError;
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index c93cb4c..c1caa56 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -332,22 +332,6 @@
],
}
-android_filesystem {
- name: "microdroid_gki_modules-6.1-arm64",
- deps: [
- "microdroid_gki_kernel_modules-6.1-arm64",
- ],
- type: "compressed_cpio",
-}
-
-android_filesystem {
- name: "microdroid_gki_modules-6.1-x86_64",
- deps: [
- "microdroid_gki_kernel_modules-6.1-x86_64",
- ],
- type: "compressed_cpio",
-}
-
genrule {
name: "microdroid_bootconfig_arm64_gen",
srcs: [
@@ -410,6 +394,16 @@
partitions: ["microdroid_vendor"],
},
},
+ // TODO(b/312809093): Remove hard-coded property after figuring out the
+ // long-term solution for microdroid vendor partition SPL. The hard-coded
+ // value is the minimum value of SPL that microdroid vendor partition will
+ // have. It's for passing the check 'IsStandaloneImageRollback'.
+ avb_properties: [
+ {
+ key: "com.android.build.microdroid-vendor.security_patch",
+ value: "2023-12-05",
+ },
+ ],
}
prebuilt_etc {
@@ -418,11 +412,6 @@
}
prebuilt_etc {
- name: "microdroid_gki.json",
- src: "microdroid_gki.json",
-}
-
-prebuilt_etc {
name: "microdroid_manifest",
src: "microdroid_manifest.xml",
filename: "manifest.xml",
@@ -450,11 +439,8 @@
// python -c "import hashlib; print(hashlib.sha256(b'initrd_normal').hexdigest())"
initrd_normal_salt = "8041a07d54ac82290f6d90bac1fa8d7fdbc4db974d101d60faf294749d1ebaf8"
-avb_gen_vbmeta_image {
- name: "microdroid_initrd_normal_hashdesc",
- src: ":microdroid_initrd_normal",
- partition_name: "initrd_normal",
- salt: initrd_normal_salt,
+avb_gen_vbmeta_image_defaults {
+ name: "microdroid_initrd_defaults",
enabled: false,
arch: {
// Microdroid kernel is only available in these architectures.
@@ -467,63 +453,38 @@
},
}
-avb_gen_vbmeta_image {
- name: "microdroid_gki_initrd_normal_hashdesc",
- src: ":microdroid_gki_initrd_normal",
+avb_gen_vbmeta_image_defaults {
+ name: "microdroid_initrd_normal_defaults",
+ defaults: ["microdroid_initrd_defaults"],
partition_name: "initrd_normal",
salt: initrd_normal_salt,
- enabled: false,
- arch: {
- // Microdroid kernel is only available in these architectures.
- arm64: {
- enabled: true,
- },
- x86_64: {
- enabled: true,
- },
- },
+}
+
+avb_gen_vbmeta_image {
+ name: "microdroid_initrd_normal_hashdesc",
+ defaults: ["microdroid_initrd_normal_defaults"],
+ src: ":microdroid_initrd_normal",
}
// python -c "import hashlib; print(hashlib.sha256(b'initrd_debug').hexdigest())"
initrd_debug_salt = "8ab9dc9cb7e6456700ff6ef18c6b4c3acc24c5fa5381b829563f8d7a415d869a"
-avb_gen_vbmeta_image {
- name: "microdroid_initrd_debug_hashdesc",
- src: ":microdroid_initrd_debuggable",
+avb_gen_vbmeta_image_defaults {
+ name: "microdroid_initrd_debug_defaults",
+ defaults: ["microdroid_initrd_defaults"],
partition_name: "initrd_debug",
salt: initrd_debug_salt,
- enabled: false,
- arch: {
- // Microdroid kernel is only available in these architectures.
- arm64: {
- enabled: true,
- },
- x86_64: {
- enabled: true,
- },
- },
}
avb_gen_vbmeta_image {
- name: "microdroid_gki_initrd_debug_hashdesc",
- src: ":microdroid_gki_initrd_debuggable",
- partition_name: "initrd_debug",
- salt: initrd_debug_salt,
- enabled: false,
- arch: {
- // Microdroid kernel is only available in these architectures.
- arm64: {
- enabled: true,
- },
- x86_64: {
- enabled: true,
- },
- },
+ name: "microdroid_initrd_debug_hashdesc",
+ defaults: ["microdroid_initrd_debug_defaults"],
+ src: ":microdroid_initrd_debuggable",
}
soong_config_module_type {
- name: "flag_aware_avb_add_hash_footer",
- module_type: "avb_add_hash_footer",
+ name: "flag_aware_avb_add_hash_footer_defaults",
+ module_type: "avb_add_hash_footer_defaults",
config_namespace: "ANDROID",
bool_variables: [
"release_avf_enable_llpvm_changes",
@@ -534,28 +495,21 @@
],
}
-flag_aware_avb_add_hash_footer {
- name: "microdroid_kernel_signed",
+flag_aware_avb_add_hash_footer_defaults {
+ name: "microdroid_kernel_signed_defaults",
src: ":empty_file",
- filename: "microdroid_kernel",
partition_name: "boot",
private_key: ":microdroid_sign_key",
salt: bootloader_salt,
enabled: false,
arch: {
arm64: {
- src: ":microdroid_kernel_prebuilts-6.1-arm64",
enabled: true,
},
x86_64: {
- src: ":microdroid_kernel_prebuilts-6.1-x86_64",
enabled: true,
},
},
- include_descriptors_from_images: [
- ":microdroid_initrd_normal_hashdesc",
- ":microdroid_initrd_debug_hashdesc",
- ],
// Below are properties that are conditionally set depending on value of build flags.
soong_config_variables: {
release_avf_enable_llpvm_changes: {
@@ -570,40 +524,22 @@
},
}
-flag_aware_avb_add_hash_footer {
- name: "microdroid_gki_kernel_signed",
- src: ":empty_file",
- filename: "microdroid_gki_kernel",
- partition_name: "boot",
- private_key: ":microdroid_sign_key",
- salt: bootloader_salt,
- enabled: false,
+avb_add_hash_footer {
+ name: "microdroid_kernel_signed",
+ defaults: ["microdroid_kernel_signed_defaults"],
+ filename: "microdroid_kernel",
arch: {
arm64: {
- src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
- enabled: true,
+ src: ":microdroid_kernel_prebuilts-6.1-arm64",
},
x86_64: {
- src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
- enabled: true,
+ src: ":microdroid_kernel_prebuilts-6.1-x86_64",
},
},
include_descriptors_from_images: [
- ":microdroid_gki_initrd_normal_hashdesc",
- ":microdroid_gki_initrd_debug_hashdesc",
+ ":microdroid_initrd_normal_hashdesc",
+ ":microdroid_initrd_debug_hashdesc",
],
- // Below are properties that are conditionally set depending on value of build flags.
- soong_config_variables: {
- release_avf_enable_llpvm_changes: {
- rollback_index: 1,
- props: [
- {
- name: "com.android.virt.cap",
- value: "secretkeeper_protection",
- },
- ],
- },
- },
}
prebuilt_etc {
@@ -620,16 +556,54 @@
},
}
+///////////////////////////////////////
+// GKI-6.1 modules
+///////////////////////////////////////
prebuilt_etc {
- name: "microdroid_gki_kernel",
+ name: "microdroid_gki-6.1.json",
+ src: "microdroid_gki-6.1.json",
+}
+
+avb_add_hash_footer {
+ name: "microdroid_gki-6.1_kernel_signed",
+ defaults: ["microdroid_kernel_signed_defaults"],
+ filename: "microdroid_gki-6.1_kernel",
+ arch: {
+ arm64: {
+ src: ":microdroid_gki_kernel_prebuilts-6.1-arm64",
+ },
+ x86_64: {
+ src: ":microdroid_gki_kernel_prebuilts-6.1-x86_64",
+ },
+ },
+ include_descriptors_from_images: [
+ ":microdroid_gki-6.1_initrd_normal_hashdesc",
+ ":microdroid_gki-6.1_initrd_debug_hashdesc",
+ ],
+}
+
+prebuilt_etc {
+ name: "microdroid_gki-6.1_kernel",
src: ":empty_file",
relative_install_path: "fs",
arch: {
arm64: {
- src: ":microdroid_gki_kernel_signed",
+ src: ":microdroid_gki-6.1_kernel_signed",
},
x86_64: {
- src: ":microdroid_gki_kernel_signed",
+ src: ":microdroid_gki-6.1_kernel_signed",
},
},
}
+
+avb_gen_vbmeta_image {
+ name: "microdroid_gki-6.1_initrd_normal_hashdesc",
+ defaults: ["microdroid_initrd_normal_defaults"],
+ src: ":microdroid_gki-6.1_initrd_normal",
+}
+
+avb_gen_vbmeta_image {
+ name: "microdroid_gki-6.1_initrd_debug_hashdesc",
+ defaults: ["microdroid_initrd_debug_defaults"],
+ src: ":microdroid_gki-6.1_initrd_debuggable",
+}
diff --git a/microdroid/initrd/Android.bp b/microdroid/initrd/Android.bp
index 6cd84fa..8df4c0f 100644
--- a/microdroid/initrd/Android.bp
+++ b/microdroid/initrd/Android.bp
@@ -41,7 +41,7 @@
}
genrule {
- name: "microdroid_gki_initrd_gen_arm64",
+ name: "microdroid_gki-6.1_initrd_gen_arm64",
srcs: [
":microdroid_ramdisk",
":microdroid_fstab_ramdisk",
@@ -52,7 +52,7 @@
}
genrule {
- name: "microdroid_gki_initrd_gen_x86_64",
+ name: "microdroid_gki-6.1_initrd_gen_x86_64",
srcs: [
":microdroid_ramdisk",
":microdroid_fstab_ramdisk",
@@ -96,13 +96,13 @@
}
genrule {
- name: "microdroid_gki_initrd_debuggable_arm64",
+ name: "microdroid_gki-6.1_initrd_debuggable_arm64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki_initrd_gen_arm64",
+ ":microdroid_gki-6.1_initrd_gen_arm64",
":microdroid_bootconfig_debuggable_src",
] + bootconfigs_arm64,
- out: ["microdroid_gki_initrd_debuggable_arm64"],
+ out: ["microdroid_gki-6.1_initrd_debuggable_arm64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -118,13 +118,13 @@
}
genrule {
- name: "microdroid_gki_initrd_debuggable_x86_64",
+ name: "microdroid_gki-6.1_initrd_debuggable_x86_64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki_initrd_gen_x86_64",
+ ":microdroid_gki-6.1_initrd_gen_x86_64",
":microdroid_bootconfig_debuggable_src",
] + bootconfigs_x86_64,
- out: ["microdroid_gki_initrd_debuggable_x86_64"],
+ out: ["microdroid_gki-6.1_initrd_debuggable_x86_64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -140,13 +140,13 @@
}
genrule {
- name: "microdroid_gki_initrd_normal_arm64",
+ name: "microdroid_gki-6.1_initrd_normal_arm64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki_initrd_gen_arm64",
+ ":microdroid_gki-6.1_initrd_gen_arm64",
":microdroid_bootconfig_normal_src",
] + bootconfigs_arm64,
- out: ["microdroid_gki_initrd_normal_arm64"],
+ out: ["microdroid_gki-6.1_initrd_normal_arm64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -162,13 +162,13 @@
}
genrule {
- name: "microdroid_gki_initrd_normal_x86_64",
+ name: "microdroid_gki-6.1_initrd_normal_x86_64",
tools: ["initrd_bootconfig"],
srcs: [
- ":microdroid_gki_initrd_gen_x86_64",
+ ":microdroid_gki-6.1_initrd_gen_x86_64",
":microdroid_bootconfig_normal_src",
] + bootconfigs_x86_64,
- out: ["microdroid_gki_initrd_normal_x86_64"],
+ out: ["microdroid_gki-6.1_initrd_normal_x86_64"],
cmd: "$(location initrd_bootconfig) attach --output $(out) $(in)",
}
@@ -188,18 +188,18 @@
}
prebuilt_etc {
- name: "microdroid_gki_initrd_debuggable",
+ name: "microdroid_gki-6.1_initrd_debuggable",
// We don't have ramdisk for architectures other than x86_64 & arm64
src: ":empty_file",
arch: {
x86_64: {
- src: ":microdroid_gki_initrd_debuggable_x86_64",
+ src: ":microdroid_gki-6.1_initrd_debuggable_x86_64",
},
arm64: {
- src: ":microdroid_gki_initrd_debuggable_arm64",
+ src: ":microdroid_gki-6.1_initrd_debuggable_arm64",
},
},
- filename: "microdroid_gki_initrd_debuggable.img",
+ filename: "microdroid_gki-6.1_initrd_debuggable.img",
}
prebuilt_etc {
@@ -218,16 +218,16 @@
}
prebuilt_etc {
- name: "microdroid_gki_initrd_normal",
+ name: "microdroid_gki-6.1_initrd_normal",
// We don't have ramdisk for architectures other than x86_64 & arm64
src: ":empty_file",
arch: {
x86_64: {
- src: ":microdroid_gki_initrd_normal_x86_64",
+ src: ":microdroid_gki-6.1_initrd_normal_x86_64",
},
arm64: {
- src: ":microdroid_gki_initrd_normal_arm64",
+ src: ":microdroid_gki-6.1_initrd_normal_arm64",
},
},
- filename: "microdroid_gki_initrd_normal.img",
+ filename: "microdroid_gki-6.1_initrd_normal.img",
}
diff --git a/microdroid/microdroid_gki.json b/microdroid/microdroid_gki-6.1.json
similarity index 84%
rename from microdroid/microdroid_gki.json
rename to microdroid/microdroid_gki-6.1.json
index d7ba53e..2115e51 100644
--- a/microdroid/microdroid_gki.json
+++ b/microdroid/microdroid_gki-6.1.json
@@ -1,5 +1,5 @@
{
- "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki_kernel",
+ "kernel": "/apex/com.android.virt/etc/fs/microdroid_gki-6.1_kernel",
"disks": [
{
"partitions": [
diff --git a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
index 51796f1..4813b35 100644
--- a/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
+++ b/microdroid_manager/aidl/android/system/virtualization/payload/IVmPayloadService.aidl
@@ -59,8 +59,8 @@
* Sequence of DER-encoded X.509 certificates that make up the attestation
* key's certificate chain.
*
- * The certificate chain starts with a root certificate and ends with a leaf
- * certificate covering the attested public key.
+ * The certificate chain starts with a leaf certificate covering the attested
+ * public key and ends with a root certificate.
*/
Certificate[] certificateChain;
}
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 103619f..f49bbce 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -45,22 +45,69 @@
cmd: "touch $(out)",
}
-rust_test {
- name: "libpvmfw.bootargs.test",
- host_supported: true,
- // For now, only bootargs.rs is written to be conditionally compiled with std.
- srcs: ["src/bootargs.rs"],
+rust_defaults {
+ name: "libpvmfw.test.defaults",
defaults: ["avf_build_flags_rust"],
test_suites: ["general-tests"],
test_options: {
unit_test: true,
},
+ prefer_rlib: true,
rustlibs: [
"libcstr",
+ ],
+}
+
+rust_test {
+ name: "libpvmfw.bootargs.test",
+ host_supported: true,
+ // For now, only bootargs.rs is written to be conditionally compiled with std.
+ srcs: ["src/bootargs.rs"],
+ defaults: ["libpvmfw.test.defaults"],
+ rustlibs: [
"libzeroize",
],
}
+rust_test {
+ name: "libpvmfw.device_assignment.test",
+ srcs: ["src/device_assignment.rs"],
+ defaults: ["libpvmfw.test.defaults"],
+ rustlibs: [
+ "liblibfdt",
+ "liblog_rust",
+ "libpvmfw_fdt_template",
+ ],
+ data: [
+ ":test_pvmfw_devices_vm_dtbo",
+ ":test_pvmfw_devices_vm_dtbo_without_symbols",
+ ":test_pvmfw_devices_with_rng",
+ ":test_pvmfw_devices_with_rng_iommu",
+ ":test_pvmfw_devices_with_multiple_devices_iommus",
+ ":test_pvmfw_devices_with_iommu_sharing",
+ ":test_pvmfw_devices_with_iommu_id_conflict",
+ ],
+ // To use libpvmfw_fdt_template for testing
+ enabled: false,
+ target: {
+ android_arm64: {
+ enabled: true,
+ },
+ },
+}
+
+rust_test {
+ name: "libpvmfw.dice.test",
+ srcs: ["src/dice.rs"],
+ defaults: ["libpvmfw.test.defaults"],
+ rustlibs: [
+ "libcbor_util",
+ "libciborium",
+ "libdiced_open_dice_nostd",
+ "libpvmfw_avb_nostd",
+ ],
+}
+
genrule {
name: "test_pvmfw_devices_vm_dtbo",
defaults: ["dts_to_dtb"],
@@ -110,39 +157,6 @@
out: ["test_pvmfw_devices_with_iommu_id_conflict.dtb"],
}
-rust_test {
- name: "libpvmfw.device_assignment.test",
- srcs: ["src/device_assignment.rs"],
- defaults: ["avf_build_flags_rust"],
- test_suites: ["general-tests"],
- test_options: {
- unit_test: true,
- },
- prefer_rlib: true,
- rustlibs: [
- "libcstr",
- "liblibfdt",
- "liblog_rust",
- "libpvmfw_fdt_template",
- ],
- data: [
- ":test_pvmfw_devices_vm_dtbo",
- ":test_pvmfw_devices_vm_dtbo_without_symbols",
- ":test_pvmfw_devices_with_rng",
- ":test_pvmfw_devices_with_rng_iommu",
- ":test_pvmfw_devices_with_multiple_devices_iommus",
- ":test_pvmfw_devices_with_iommu_sharing",
- ":test_pvmfw_devices_with_iommu_id_conflict",
- ],
- // To use libpvmfw_fdt_template for testing
- enabled: false,
- target: {
- android_arm64: {
- enabled: true,
- },
- },
-}
-
cc_binary {
name: "pvmfw",
defaults: ["vmbase_elf_defaults"],
diff --git a/pvmfw/TEST_MAPPING b/pvmfw/TEST_MAPPING
index f21318e..e948400 100644
--- a/pvmfw/TEST_MAPPING
+++ b/pvmfw/TEST_MAPPING
@@ -10,6 +10,9 @@
},
{
"name" : "libpvmfw.device_assignment.test"
+ },
+ {
+ "name" : "libpvmfw.dice.test"
}
]
}
diff --git a/pvmfw/avb/src/error.rs b/pvmfw/avb/src/error.rs
index af38c54..0f052e8 100644
--- a/pvmfw/avb/src/error.rs
+++ b/pvmfw/avb/src/error.rs
@@ -19,21 +19,20 @@
/// Wrapper around `avb::SlotVerifyError` to add custom pvmfw errors.
/// It is the error thrown by the payload verification API `verify_payload()`.
-#[derive(Clone, Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq)]
pub enum PvmfwVerifyError {
- /// Passthrough avb::SlotVerifyError.
- AvbError(avb::SlotVerifyError),
+ /// Passthrough `avb::SlotVerifyError` with no `SlotVerifyData`.
+ AvbError(avb::SlotVerifyError<'static>),
/// VBMeta has invalid descriptors.
InvalidDescriptors(avb::IoError),
/// Unknown vbmeta property.
UnknownVbmetaProperty,
}
-/// It's always possible to convert from an `avb::SlotVerifyError` since we are
-/// a superset.
-impl From<avb::SlotVerifyError> for PvmfwVerifyError {
+impl From<avb::SlotVerifyError<'_>> for PvmfwVerifyError {
fn from(error: avb::SlotVerifyError) -> Self {
- Self::AvbError(error)
+ // We don't use verification data on failure, drop it to get a `'static` lifetime.
+ Self::AvbError(error.without_verify_data())
}
}
diff --git a/pvmfw/avb/src/ops.rs b/pvmfw/avb/src/ops.rs
index c7b8b01..aee93c8 100644
--- a/pvmfw/avb/src/ops.rs
+++ b/pvmfw/avb/src/ops.rs
@@ -94,7 +94,7 @@
pub(crate) fn verify_partition(
&mut self,
partition_name: &CStr,
- ) -> Result<AvbSlotVerifyDataWrap, avb::SlotVerifyError> {
+ ) -> Result<AvbSlotVerifyDataWrap, avb::SlotVerifyError<'static>> {
let requested_partitions = [partition_name.as_ptr(), ptr::null()];
let ab_suffix = CStr::from_bytes_with_nul(NULL_BYTE).unwrap();
let mut out_data = MaybeUninit::uninit();
@@ -292,7 +292,7 @@
pub(crate) struct AvbSlotVerifyDataWrap(*mut AvbSlotVerifyData);
impl TryFrom<*mut AvbSlotVerifyData> for AvbSlotVerifyDataWrap {
- type Error = avb::SlotVerifyError;
+ type Error = avb::SlotVerifyError<'static>;
fn try_from(data: *mut AvbSlotVerifyData) -> Result<Self, Self::Error> {
is_not_null(data).map_err(|_| avb::SlotVerifyError::Io)?;
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index 492d387..3274033 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -94,7 +94,7 @@
fn verify_only_one_vbmeta_exists(
vbmeta_images: &[AvbVBMetaData],
-) -> Result<(), avb::SlotVerifyError> {
+) -> Result<(), avb::SlotVerifyError<'static>> {
if vbmeta_images.len() == 1 {
Ok(())
} else {
@@ -104,7 +104,7 @@
fn verify_vbmeta_is_from_kernel_partition(
vbmeta_image: &AvbVBMetaData,
-) -> Result<(), avb::SlotVerifyError> {
+) -> Result<(), avb::SlotVerifyError<'static>> {
match (vbmeta_image.partition_name as *const c_char).try_into() {
Ok(PartitionName::Kernel) => Ok(()),
_ => Err(avb::SlotVerifyError::InvalidMetadata),
@@ -113,7 +113,7 @@
fn verify_vbmeta_has_only_one_hash_descriptor(
descriptors: &Descriptors,
-) -> Result<(), avb::SlotVerifyError> {
+) -> Result<(), avb::SlotVerifyError<'static>> {
if descriptors.num_hash_descriptor() == 1 {
Ok(())
} else {
@@ -125,7 +125,7 @@
loaded_partitions: &[AvbPartitionData],
partition_name: PartitionName,
expected_len: usize,
-) -> Result<(), avb::SlotVerifyError> {
+) -> Result<(), avb::SlotVerifyError<'static>> {
if loaded_partitions.len() != 1 {
// Only one partition should be loaded in each verify result.
return Err(avb::SlotVerifyError::Io);
@@ -140,7 +140,7 @@
if loaded_partition.data_size == expected_len {
Ok(())
} else {
- Err(avb::SlotVerifyError::Verification)
+ Err(avb::SlotVerifyError::Verification(None))
}
}
@@ -202,7 +202,7 @@
} else if let Ok(result) = ops.verify_partition(PartitionName::InitrdDebug.as_cstr()) {
(DebugLevel::Full, result, PartitionName::InitrdDebug)
} else {
- return Err(avb::SlotVerifyError::Verification.into());
+ return Err(avb::SlotVerifyError::Verification(None).into());
};
let loaded_partitions = initrd_verify_result.loaded_partitions()?;
verify_loaded_partition_has_expected_length(
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index 6344433..84f83c2 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -211,7 +211,7 @@
&load_latest_signed_kernel()?,
/* initrd= */ &fs::read(UNSIGNED_TEST_IMG_PATH)?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification.into(),
+ avb::SlotVerifyError::Verification(None).into(),
)
}
@@ -234,7 +234,7 @@
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification.into(),
+ avb::SlotVerifyError::Verification(None).into(),
)
}
@@ -301,7 +301,7 @@
&load_latest_signed_kernel()?,
&initrd,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification.into(),
+ avb::SlotVerifyError::Verification(None).into(),
)
}
@@ -340,13 +340,13 @@
&kernel,
&load_latest_initrd_normal()?,
&empty_public_key,
- avb::SlotVerifyError::Verification.into(),
+ avb::SlotVerifyError::Verification(None).into(),
)?;
assert_payload_verification_with_initrd_fails(
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification.into(),
+ avb::SlotVerifyError::Verification(None).into(),
)
}
@@ -384,7 +384,7 @@
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification.into(),
+ avb::SlotVerifyError::Verification(None).into(),
)
}
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 112c24c..99bf589 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -14,16 +14,13 @@
//! Support for DICE derivation and BCC generation.
-use core::ffi::c_void;
use core::mem::size_of;
-use core::slice;
use cstr::cstr;
use diced_open_dice::{
bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
Hash, InputValues, HIDDEN_SIZE,
};
-use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
-use vmbase::memory::flushed_zeroize;
+use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
match debug_level {
@@ -46,6 +43,7 @@
pub auth_hash: Hash,
pub mode: DiceMode,
pub security_version: u64,
+ pub rkp_vm_marker: bool,
}
impl PartialInputs {
@@ -55,8 +53,9 @@
let mode = to_dice_mode(data.debug_level);
// We use rollback_index from vbmeta as the security_version field in dice certificate.
let security_version = data.rollback_index;
+ let rkp_vm_marker = data.has_capability(Capability::RemoteAttest);
- Ok(Self { code_hash, auth_hash, mode, security_version })
+ Ok(Self { code_hash, auth_hash, mode, security_version, rkp_vm_marker })
}
pub fn write_next_bcc(
@@ -66,15 +65,7 @@
next_bcc: &mut [u8],
) -> diced_open_dice::Result<()> {
let mut config_descriptor_buffer = [0; 128];
- let config_values = DiceConfigValues {
- component_name: Some(cstr!("vm_entry")),
- security_version: if cfg!(llpvm_changes) { Some(self.security_version) } else { None },
- ..Default::default()
- };
-
- let config_descriptor_size =
- bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
- let config = &config_descriptor_buffer[..config_descriptor_size];
+ let config = self.generate_config_descriptor(&mut config_descriptor_buffer)?;
let dice_inputs = InputValues::new(
self.code_hash,
@@ -86,17 +77,138 @@
let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc)?;
Ok(())
}
+
+ fn generate_config_descriptor<'a>(
+ &self,
+ config_descriptor_buffer: &'a mut [u8],
+ ) -> diced_open_dice::Result<&'a [u8]> {
+ let config_values = DiceConfigValues {
+ component_name: Some(cstr!("vm_entry")),
+ security_version: if cfg!(dice_changes) { Some(self.security_version) } else { None },
+ rkp_vm_marker: self.rkp_vm_marker,
+ ..Default::default()
+ };
+ let config_descriptor_size =
+ bcc_format_config_descriptor(&config_values, config_descriptor_buffer)?;
+ let config = &config_descriptor_buffer[..config_descriptor_size];
+ Ok(config)
+ }
}
/// Flushes data caches over the provided address range.
///
/// # Safety
///
-/// The provided address and size must be to a valid address range (typically on the stack, .bss,
-/// .data, or provided BCC).
+/// The provided address and size must be to an address range that is valid for read and write
+/// (typically on the stack, .bss, .data, or provided BCC) from a single allocation
+/// (e.g. stack array).
#[no_mangle]
-unsafe extern "C" fn DiceClearMemory(_ctx: *mut c_void, size: usize, addr: *mut c_void) {
- // SAFETY: We must trust that the slice will be valid arrays/variables on the C code stack.
+#[cfg(not(test))]
+unsafe extern "C" fn DiceClearMemory(
+ _ctx: *mut core::ffi::c_void,
+ size: usize,
+ addr: *mut core::ffi::c_void,
+) {
+ use core::slice;
+ use vmbase::memory::flushed_zeroize;
+
+ // SAFETY: We require our caller to provide a valid range within a single object. The open-dice
+ // always calls this on individual stack-allocated arrays which ensures that.
let region = unsafe { slice::from_raw_parts_mut(addr as *mut u8, size) };
flushed_zeroize(region)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use ciborium::Value;
+ use std::collections::HashMap;
+ use std::vec;
+
+ const COMPONENT_NAME_KEY: i64 = -70002;
+ const COMPONENT_VERSION_KEY: i64 = -70003;
+ const RESETTABLE_KEY: i64 = -70004;
+ const SECURITY_VERSION_KEY: i64 = -70005;
+ const RKP_VM_MARKER_KEY: i64 = -70006;
+
+ const BASE_VB_DATA: VerifiedBootData = VerifiedBootData {
+ debug_level: DebugLevel::None,
+ kernel_digest: [1u8; size_of::<Digest>()],
+ initrd_digest: Some([2u8; size_of::<Digest>()]),
+ public_key: b"public key",
+ capabilities: vec![],
+ rollback_index: 42,
+ };
+
+ #[test]
+ fn base_data_conversion() {
+ let vb_data = BASE_VB_DATA;
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+
+ assert_eq!(inputs.mode, DiceMode::kDiceModeNormal);
+ assert_eq!(inputs.security_version, 42);
+ assert!(!inputs.rkp_vm_marker);
+
+ // TODO(b/313608219): Consider checks for code_hash and possibly auth_hash.
+ }
+
+ #[test]
+ fn debuggable_conversion() {
+ let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+
+ assert_eq!(inputs.mode, DiceMode::kDiceModeDebug);
+ }
+
+ #[test]
+ fn rkp_vm_conversion() {
+ let vb_data =
+ VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+
+ assert!(inputs.rkp_vm_marker);
+ }
+
+ #[test]
+ fn base_config_descriptor() {
+ let vb_data = BASE_VB_DATA;
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+ let config_map = decode_config_descriptor(&inputs);
+
+ assert_eq!(config_map.get(&COMPONENT_NAME_KEY).unwrap().as_text().unwrap(), "vm_entry");
+ assert_eq!(config_map.get(&COMPONENT_VERSION_KEY), None);
+ assert_eq!(config_map.get(&RESETTABLE_KEY), None);
+ if cfg!(dice_changes) {
+ assert_eq!(
+ config_map.get(&SECURITY_VERSION_KEY).unwrap().as_integer().unwrap(),
+ 42.into()
+ );
+ } else {
+ assert_eq!(config_map.get(&SECURITY_VERSION_KEY), None);
+ }
+ assert_eq!(config_map.get(&RKP_VM_MARKER_KEY), None);
+ }
+
+ #[test]
+ fn config_descriptor_with_rkp_vm() {
+ let vb_data =
+ VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+ let config_map = decode_config_descriptor(&inputs);
+
+ assert!(config_map.get(&RKP_VM_MARKER_KEY).unwrap().is_null());
+ }
+
+ fn decode_config_descriptor(inputs: &PartialInputs) -> HashMap<i64, Value> {
+ let mut buffer = [0; 128];
+ let config_descriptor = inputs.generate_config_descriptor(&mut buffer).unwrap();
+
+ let cbor_map =
+ cbor_util::deserialize::<Value>(config_descriptor).unwrap().into_map().unwrap();
+
+ cbor_map
+ .into_iter()
+ .map(|(k, v)| ((k.into_integer().unwrap().try_into().unwrap()), v))
+ .collect()
+ }
+}
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 728c1eb..1ab02e9 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -109,17 +109,21 @@
"android.system.virtualizationservice-rust",
"libandroid_logger",
"libanyhow",
+ "libbssl_avf_nostd",
"libciborium",
"libclient_vm_csr",
+ "libcoset",
"libdiced_sample_inputs",
"liblibc",
"liblog_rust",
"libservice_vm_comm",
"libservice_vm_manager",
"libvmclient",
+ "libx509_parser",
],
data: [
":rialto_unsigned",
+ ":test_rkp_cert_chain",
],
test_suites: ["general-tests"],
enabled: false,
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index 0f59350..85c3efe 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -22,22 +22,32 @@
binder::{ParcelFileDescriptor, ProcessState},
};
use anyhow::{bail, Context, Result};
+use bssl_avf::{sha256, EcKey, EvpPKey};
use ciborium::value::Value;
use client_vm_csr::generate_attestation_key_and_csr;
+use coset::{CborSerializable, CoseMac0, CoseSign};
use log::info;
use service_vm_comm::{
- ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
- RequestProcessingError, Response, VmType,
+ ClientVmAttestationParams, Csr, CsrPayload, EcdsaP256KeyPair, GenerateCertificateRequestParams,
+ Request, Response, VmType,
};
use service_vm_manager::ServiceVm;
+use std::fs;
use std::fs::File;
use std::io;
use std::panic;
use std::path::PathBuf;
use vmclient::VmInstance;
+use x509_parser::{
+ certificate::X509Certificate,
+ der_parser::{der::parse_der, oid, oid::Oid},
+ prelude::FromDer,
+ x509::{AlgorithmIdentifier, SubjectPublicKeyInfo, X509Version},
+};
const UNSIGNED_RIALTO_PATH: &str = "/data/local/tmp/rialto_test/arm64/rialto_unsigned.bin";
const INSTANCE_IMG_PATH: &str = "/data/local/tmp/rialto_test/arm64/instance.img";
+const TEST_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
#[test]
fn process_requests_in_protected_vm() -> Result<()> {
@@ -55,7 +65,7 @@
check_processing_reverse_request(&mut vm)?;
let key_pair = check_processing_generating_key_pair_request(&mut vm)?;
check_processing_generating_certificate_request(&mut vm, &key_pair.maced_public_key)?;
- check_attestation_request(&mut vm, &key_pair.key_blob)?;
+ check_attestation_request(&mut vm, &key_pair)?;
Ok(())
}
@@ -110,7 +120,10 @@
}
}
-fn check_attestation_request(vm: &mut ServiceVm, key_blob: &[u8]) -> Result<()> {
+fn check_attestation_request(
+ vm: &mut ServiceVm,
+ remotely_provisioned_key_pair: &EcdsaP256KeyPair,
+) -> Result<()> {
/// The following data was generated randomly with urandom.
const CHALLENGE: [u8; 16] = [
0x7d, 0x86, 0x58, 0x79, 0x3a, 0x09, 0xdf, 0x1c, 0xa5, 0x80, 0x80, 0x15, 0x2b, 0x13, 0x17,
@@ -118,10 +131,18 @@
];
let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+ let cert_chain = fs::read(TEST_CERT_CHAIN_PATH)?;
+ let (remaining, cert) = X509Certificate::from_der(&cert_chain)?;
+ // Builds the mock parameters for the client VM attestation.
+ // The `csr` and `remotely_provisioned_key_blob` parameters are extracted from the same
+ // libraries as in production.
+ // The `remotely_provisioned_cert` parameter is an RKP certificate extracted from a test
+ // certificate chain retrieved from RKPD.
let params = ClientVmAttestationParams {
- csr: attestation_data.csr.into_cbor_vec()?,
- remotely_provisioned_key_blob: key_blob.to_vec(),
+ csr: attestation_data.csr.clone().into_cbor_vec()?,
+ remotely_provisioned_key_blob: remotely_provisioned_key_pair.key_blob.to_vec(),
+ remotely_provisioned_cert: cert_chain[..(cert_chain.len() - remaining.len())].to_vec(),
};
let request = Request::RequestClientVmAttestation(params);
@@ -129,12 +150,76 @@
info!("Received response: {response:?}.");
match response {
- // TODO(b/309441500): Check the certificate once it is implemented.
- Response::Err(RequestProcessingError::OperationUnimplemented) => Ok(()),
+ Response::RequestClientVmAttestation(certificate) => {
+ check_certificate_for_client_vm(
+ &certificate,
+ &remotely_provisioned_key_pair.maced_public_key,
+ &attestation_data.csr,
+ &cert,
+ )?;
+ Ok(())
+ }
_ => bail!("Incorrect response type: {response:?}"),
}
}
+fn check_certificate_for_client_vm(
+ certificate: &[u8],
+ maced_public_key: &[u8],
+ csr: &Csr,
+ parent_certificate: &X509Certificate,
+) -> Result<()> {
+ let cose_mac = CoseMac0::from_slice(maced_public_key)?;
+ let authority_public_key = EcKey::from_cose_public_key(&cose_mac.payload.unwrap()).unwrap();
+ let (remaining, cert) = X509Certificate::from_der(certificate)?;
+ assert!(remaining.is_empty());
+
+ // Checks the certificate signature against the authority public key.
+ const ECDSA_WITH_SHA_256: Oid<'static> = oid!(1.2.840 .10045 .4 .3 .2);
+ let expected_algorithm =
+ AlgorithmIdentifier { algorithm: ECDSA_WITH_SHA_256, parameters: None };
+ assert_eq!(expected_algorithm, cert.signature_algorithm);
+ let digest = sha256(cert.tbs_certificate.as_ref()).unwrap();
+ authority_public_key
+ .ecdsa_verify(cert.signature_value.as_ref(), &digest)
+ .expect("Failed to verify the certificate signature with the authority public key");
+
+ // Checks that the certificate's subject public key is equal to the key in the CSR.
+ let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
+ let csr_payload =
+ cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
+ let subject_public_key = EcKey::from_cose_public_key(&csr_payload.public_key).unwrap();
+ let expected_spki_data =
+ EvpPKey::try_from(subject_public_key).unwrap().subject_public_key_info().unwrap();
+ let (remaining, expected_spki) = SubjectPublicKeyInfo::from_der(&expected_spki_data)?;
+ assert!(remaining.is_empty());
+ assert_eq!(&expected_spki, cert.public_key());
+
+ // Checks the certificate extension.
+ const ATTESTATION_EXTENSION_OID: Oid<'static> = oid!(1.3.6 .1 .4 .1 .11129 .2 .1 .29 .1);
+ let extensions = cert.extensions();
+ assert_eq!(1, extensions.len());
+ let extension = &extensions[0];
+ assert_eq!(ATTESTATION_EXTENSION_OID, extension.oid);
+ assert!(!extension.critical);
+ let (remaining, extension) = parse_der(extension.value)?;
+ assert!(remaining.is_empty());
+ let attestation_ext = extension.as_sequence()?;
+ assert_eq!(1, attestation_ext.len());
+ assert_eq!(csr_payload.challenge, attestation_ext[0].as_slice()?);
+
+ // Checks other fields on the certificate
+ assert_eq!(X509Version::V3, cert.version());
+ assert_eq!(parent_certificate.validity(), cert.validity());
+ assert_eq!(
+ String::from("CN=Android Protected Virtual Machine Key"),
+ cert.subject().to_string()
+ );
+ assert_eq!(parent_certificate.subject(), cert.issuer());
+
+ Ok(())
+}
+
/// TODO(b/300625792): Check the CSR with libhwtrust once the CSR is complete.
fn check_csr(csr: Vec<u8>) -> Result<()> {
let mut reader = io::Cursor::new(csr);
diff --git a/secretkeeper/comm/Android.bp b/secretkeeper/comm/Android.bp
new file mode 100644
index 0000000..cb3e713
--- /dev/null
+++ b/secretkeeper/comm/Android.bp
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libsecretkeeper_comm.defaults",
+ crate_name: "secretkeeper_comm",
+ defaults: ["avf_build_flags_rust"],
+ edition: "2021",
+ lints: "android",
+ rustlibs: [
+ "libciborium",
+ "libcoset",
+ ],
+ proc_macros: ["libenumn"],
+ vendor_available: true,
+}
+
+rust_library {
+ name: "libsecretkeeper_comm_nostd",
+ defaults: ["libsecretkeeper_comm.defaults"],
+ srcs: ["src/lib.rs"],
+}
+
+rust_test {
+ name: "libsecretkeeper_comm.test",
+ defaults: [
+ "libsecretkeeper_comm.defaults",
+ "rdroidtest.defaults",
+ ],
+ srcs: ["tests/*.rs"],
+ test_suites: ["general-tests"],
+ rustlibs: [
+ "libsecretkeeper_comm_nostd",
+ ],
+}
diff --git a/secretkeeper/comm/src/cbor_convert.rs b/secretkeeper/comm/src/cbor_convert.rs
new file mode 100644
index 0000000..ab6ca3f
--- /dev/null
+++ b/secretkeeper/comm/src/cbor_convert.rs
@@ -0,0 +1,46 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Implements various useful CBOR conversion method.
+
+use crate::data_types::error::Error;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Decodes the provided binary CBOR-encoded value and returns a
+/// [`ciborium::Value`] struct wrapped in Result.
+pub fn value_from_bytes(mut bytes: &[u8]) -> Result<Value, Error> {
+ let value = ciborium::de::from_reader(&mut bytes).map_err(|_| Error::ConversionError)?;
+ // Ciborium tries to read one Value, but doesn't care if there is trailing data after it. We do
+ if !bytes.is_empty() {
+ return Err(Error::ConversionError);
+ }
+ Ok(value)
+}
+
+/// Encodes a [`ciborium::Value`] into bytes.
+pub fn value_to_bytes(value: &Value) -> Result<Vec<u8>, Error> {
+ let mut bytes: Vec<u8> = Vec::new();
+ ciborium::ser::into_writer(&value, &mut bytes).map_err(|_| Error::UnexpectedError)?;
+ Ok(bytes)
+}
+
+// Useful to convert [`ciborium::Value`] to integer, we return largest integer range for
+// convenience, callers should downcast into appropriate type.
+pub fn value_to_integer(value: &Value) -> Result<i128, Error> {
+ let num = value.as_integer().ok_or(Error::ConversionError)?.into();
+ Ok(num)
+}
diff --git a/secretkeeper/comm/src/data_types/error.rs b/secretkeeper/comm/src/data_types/error.rs
new file mode 100644
index 0000000..6a5e24f
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/error.rs
@@ -0,0 +1,94 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Error-like data structures. See `ResponsePacketError` in the CDDL
+
+// derive(N) generates a method that is missing a docstring.
+#![allow(missing_docs)]
+
+use crate::cbor_convert::value_to_integer;
+use crate::data_types::response::Response;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+use enumn::N;
+
+/// 'Error code' corresponding to successful response.
+pub const ERROR_OK: u16 = 0; // All real errors must have non-zero error_codes
+
+/// Errors from Secretkeeper API. Keep in sync with `ErrorCode` defined for Secretkeeper HAL
+/// at SecretManagement.cddl
+#[derive(Clone, Copy, Debug, Eq, N, PartialEq)]
+pub enum SecretkeeperError {
+ // This is the Error code used if no other error codes explains the issue.
+ UnexpectedServerError = 1,
+ // Indicates the Request was malformed & hence couldn't be served.
+ RequestMalformed = 2,
+ // TODO(b/291228655): Add other errors such as DicePolicyError.
+}
+
+// [`SecretkeeperError`] is a valid [`Response`] type.
+// For more information see `ErrorCode` in SecretManagement.cddl alongside ISecretkeeper.aidl
+impl Response for SecretkeeperError {
+ fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error> {
+ // TODO(b/291228655): This method currently discards the second value in response_cbor,
+ // which contains additional human-readable context in error. Include it!
+ if response_cbor.is_empty() || response_cbor.len() > 2 {
+ return Err(Error::ResponseMalformed);
+ }
+ let error_code: u16 = value_to_integer(&response_cbor[0])?.try_into()?;
+ SecretkeeperError::n(error_code)
+ .map_or_else(|| Err(Error::ResponseMalformed), |sk_err| Ok(Box::new(sk_err)))
+ }
+
+ fn error_code(&self) -> u16 {
+ *self as u16
+ }
+}
+
+/// Errors thrown internally by the library.
+#[derive(Debug, PartialEq)]
+pub enum Error {
+ /// Request was malformed.
+ RequestMalformed,
+ /// Response received from the server was malformed.
+ ResponseMalformed,
+ /// An error happened when serializing to/from a [`Value`].
+ CborValueError,
+ /// An error happened while casting a type to different type,
+ /// including one [`Value`] type to another.
+ ConversionError,
+ /// These are unexpected errors, which should never really happen.
+ UnexpectedError,
+}
+
+impl From<ciborium::value::Error> for Error {
+ fn from(_e: ciborium::value::Error) -> Self {
+ Self::CborValueError
+ }
+}
+
+impl From<ciborium::Value> for Error {
+ fn from(_e: ciborium::Value) -> Self {
+ Self::ConversionError
+ }
+}
+
+impl From<core::num::TryFromIntError> for Error {
+ fn from(_e: core::num::TryFromIntError) -> Self {
+ Self::ConversionError
+ }
+}
diff --git a/secretkeeper/comm/src/data_types/mod.rs b/secretkeeper/comm/src/data_types/mod.rs
new file mode 100644
index 0000000..096777f
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/mod.rs
@@ -0,0 +1,27 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Implements the data structures specified by SecretManagement.cddl in Secretkeeper HAL.
+//! Data structures specified by SecretManagement.cddl in Secretkeeper HAL.
+//! Note this library must stay in sync with:
+//! platform/hardware/interfaces/security/\
+//! secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
+
+pub mod error;
+pub mod packet;
+pub mod request;
+pub mod request_response_impl;
+pub mod response;
diff --git a/secretkeeper/comm/src/data_types/packet.rs b/secretkeeper/comm/src/data_types/packet.rs
new file mode 100644
index 0000000..7a1e575
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/packet.rs
@@ -0,0 +1,118 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Defines the packet structures passed between functional layer & the layer below.
+
+pub use ciborium::Value;
+
+use crate::cbor_convert::{value_from_bytes, value_to_bytes, value_to_integer};
+use crate::data_types::error::Error;
+use crate::data_types::error::ERROR_OK;
+use crate::data_types::request_response_impl::Opcode;
+use alloc::vec::Vec;
+
+/// Encapsulate Request-like data that functional layer operates on. All structures
+/// that implements `data_types::request::Request` can be serialized to [`ResponsePacket`].
+/// Similarly all [`RequestPacket`] can be deserialized to concrete Requests.
+/// Keep in sync with HAL spec (in particular RequestPacket):
+/// security/secretkeeper/aidl/android/hardware/security/secretkeeper/SecretManagement.cddl
+#[derive(Clone, Debug, PartialEq)]
+pub struct RequestPacket(Vec<Value>);
+
+impl RequestPacket {
+ /// Construct a [`RequestPacket`] from array of `ciborium::Value`
+ pub fn from(request_cbor: Vec<Value>) -> Self {
+ Self(request_cbor)
+ }
+
+ /// Get the containing CBOR. This can be used for getting concrete response objects.
+ /// Keep in sync with [`crate::data_types::request::Request::serialize_to_packet()`]
+ pub fn into_inner(self) -> Vec<Value> {
+ self.0
+ }
+
+ /// Extract [`Opcode`] corresponding to this packet. As defined in by the spec, this is
+ /// the first value in the CBOR array.
+ pub fn opcode(&self) -> Result<Opcode, Error> {
+ if self.0.is_empty() {
+ return Err(Error::RequestMalformed);
+ }
+ let num: u16 = value_to_integer(&self.0[0])?.try_into()?;
+
+ Opcode::n(num).ok_or(Error::RequestMalformed)
+ }
+
+ /// Serialize the [`ResponsePacket`] to bytes
+ pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
+ value_to_bytes(&Value::Array(self.0))
+ }
+
+ /// Deserialize the bytes into [`ResponsePacket`]
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+ Ok(RequestPacket(value_from_bytes(bytes)?.into_array()?))
+ }
+}
+
+/// Encapsulate Response like data that the functional layer operates on. All structures
+/// that implements `data_types::response::Response` can be serialized to [`ResponsePacket`].
+/// Similarly all [`ResponsePacket`] can be deserialized to concrete Response.
+#[derive(Clone, Debug, PartialEq)]
+pub struct ResponsePacket(Vec<Value>);
+
+impl ResponsePacket {
+ /// Construct a [`ResponsePacket`] from array of `ciborium::Value`
+ pub fn from(response_cbor: Vec<Value>) -> Self {
+ Self(response_cbor)
+ }
+
+ /// Get raw content. This can be used for getting concrete response objects.
+ /// Keep in sync with `crate::data_types::response::Response::serialize_to_packet`
+ pub fn into_inner(self) -> Vec<Value> {
+ self.0
+ }
+
+ /// A [`ResponsePacket`] encapsulates different types of responses, find which one!
+ pub fn response_type(&self) -> Result<ResponseType, Error> {
+ if self.0.is_empty() {
+ return Err(Error::ResponseMalformed);
+ }
+ let error_code: u16 = value_to_integer(&self.0[0])?.try_into()?;
+ if error_code == ERROR_OK {
+ Ok(ResponseType::Success)
+ } else {
+ Ok(ResponseType::Error)
+ }
+ }
+
+ /// Serialize the [`ResponsePacket`] to bytes
+ pub fn into_bytes(self) -> Result<Vec<u8>, Error> {
+ value_to_bytes(&Value::Array(self.0))
+ }
+
+ /// Deserialize the bytes into [`ResponsePacket`]
+ pub fn from_bytes(bytes: &[u8]) -> Result<Self, Error> {
+ Ok(ResponsePacket(value_from_bytes(bytes)?.into_array()?))
+ }
+}
+
+/// Responses can be different type - `Success`-like or `Error`-like.
+#[derive(Debug, Eq, PartialEq)]
+pub enum ResponseType {
+ /// Indicates successful operation. See `ResponsePacketSuccess` in SecretManagement.cddl
+ Success,
+ /// Indicate failed operation. See `ResponsePacketError` in SecretManagement.cddl
+ Error,
+}
diff --git a/secretkeeper/comm/src/data_types/request.rs b/secretkeeper/comm/src/data_types/request.rs
new file mode 100644
index 0000000..0d54bcd
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/request.rs
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Defines the shared behaviour of all request like data structures.
+
+use crate::data_types::error::Error;
+use crate::data_types::packet::RequestPacket;
+use crate::data_types::request_response_impl::Opcode;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Collection of methods defined for Secretkeeper's request-like data structures,
+/// e.g. `GetVersionRequestPacket` in the HAL spec.
+///
+/// Keep in sync with SecretManagement.cddl, in particular `RequestPacket` type.
+pub trait Request {
+ /// [`Opcode`] of the request: Each Request type is associated with an opcode. See `Opcode` in
+ /// SecretManagement.cddl.
+ const OPCODE: Opcode;
+
+ /// Constructor of the [`Request`] object. Implementation of this constructor should check
+ /// the args' type adheres to the HAL spec.
+ ///
+ /// # Arguments
+ /// * `args` - The vector of arguments associated with this request. Each argument is a
+ /// `ciborium::Value` type. See `Params` in `RequestPacket` in SecretManagement.cddl
+ fn new(args: Vec<Value>) -> Result<Box<Self>, Error>;
+
+ /// Get the 'arguments' of this request.
+ fn args(&self) -> Vec<Value>;
+
+ /// Serialize the request to a [`RequestPacket`], which, as per SecretManagement.cddl is:
+ /// ```
+ /// RequestPacket<Opcode, Params> = [
+ /// Opcode,
+ /// Params
+ /// ]
+ /// ```
+ fn serialize_to_packet(&self) -> RequestPacket {
+ let mut res = self.args();
+ res.insert(0, Value::from(Self::OPCODE as u16));
+ RequestPacket::from(res)
+ }
+
+ /// Construct the [`Request`] struct from given [`RequestPacket`].
+ fn deserialize_from_packet(packet: RequestPacket) -> Result<Box<Self>, Error> {
+ let mut req = packet.into_inner();
+ if req.get(0) != Some(&Value::from(Self::OPCODE as u16)) {
+ return Err(Error::RequestMalformed);
+ }
+ req.remove(0);
+ Self::new(req)
+ }
+}
diff --git a/secretkeeper/comm/src/data_types/request_response_impl.rs b/secretkeeper/comm/src/data_types/request_response_impl.rs
new file mode 100644
index 0000000..a7d29cc
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/request_response_impl.rs
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Implementation of request & response like data structures.
+
+// derive(N) generates a method that is missing a docstring.
+#![allow(missing_docs)]
+
+use crate::cbor_convert::value_to_integer;
+use crate::data_types::error::Error;
+use crate::data_types::error::ERROR_OK;
+use crate::data_types::request::Request;
+use crate::data_types::response::Response;
+use alloc::boxed::Box;
+use alloc::vec;
+use alloc::vec::Vec;
+use ciborium::Value;
+use enumn::N;
+
+/// Set of all possible `Opcode` supported by SecretManagement API of the HAL.
+/// See `Opcode` in SecretManagement.cddl
+#[derive(Clone, Copy, Debug, N, PartialEq)]
+#[non_exhaustive]
+pub enum Opcode {
+ /// Get version of the SecretManagement API.
+ GetVersion = 1,
+ /// Store a secret
+ StoreSecret = 2,
+ /// Get the secret
+ GetSecret = 3,
+}
+
+/// Corresponds to `GetVersionRequestPacket` defined in SecretManagement.cddl
+#[derive(Debug, Eq, PartialEq)]
+pub struct GetVersionRequest;
+
+impl Request for GetVersionRequest {
+ const OPCODE: Opcode = Opcode::GetVersion;
+
+ fn new(args: Vec<Value>) -> Result<Box<Self>, Error> {
+ if !args.is_empty() {
+ return Err(Error::RequestMalformed);
+ }
+ Ok(Box::new(Self))
+ }
+
+ fn args(&self) -> Vec<Value> {
+ Vec::new()
+ }
+}
+
+/// Success response corresponding to `GetVersionResponsePacket`.
+#[derive(Debug, Eq, PartialEq)]
+pub struct GetVersionResponse {
+ /// Version of SecretManagement API
+ version: u64,
+}
+
+impl GetVersionResponse {
+ pub fn new(version: u64) -> Self {
+ Self { version }
+ }
+ pub fn version(&self) -> u64 {
+ self.version
+ }
+}
+
+impl Response for GetVersionResponse {
+ fn new(res: Vec<Value>) -> Result<Box<Self>, Error> {
+ if res.len() != 2 {
+ return Err(Error::ResponseMalformed);
+ }
+ let error_code: u16 = value_to_integer(&res[0])?.try_into()?;
+ if error_code != ERROR_OK {
+ return Err(Error::ResponseMalformed);
+ }
+ let version: u64 = value_to_integer(&res[1])?.try_into()?;
+ Ok(Box::new(Self::new(version)))
+ }
+
+ fn result(&self) -> Vec<Value> {
+ vec![self.version.into()]
+ }
+}
diff --git a/secretkeeper/comm/src/data_types/response.rs b/secretkeeper/comm/src/data_types/response.rs
new file mode 100644
index 0000000..e975ebc
--- /dev/null
+++ b/secretkeeper/comm/src/data_types/response.rs
@@ -0,0 +1,78 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Defines the shared behaviour of all response like data structures.
+
+use crate::data_types::error::{Error, ERROR_OK};
+use crate::data_types::packet::ResponsePacket;
+use alloc::boxed::Box;
+use alloc::vec::Vec;
+use ciborium::Value;
+
+/// Shared behaviour of all Secretkeeper's response-like data structures,
+/// e.g. `GetVersionResponsePacket`. Note - A valid [`Response`] can be error as well, like
+/// `SecretkeeperError::RequestMalformed`.
+///
+/// Keep in sync with SecretManagement.cddl, in particular `ResponsePacket` type.
+pub trait Response {
+ /// Constructor of the Response object.
+ /// # Arguments
+ /// * `response_cbor`: A vector of `[ciborium::Value]` such that:
+ /// ```
+ /// For success-like responses:
+ /// ResponsePacketSuccess = [
+ /// 0, ; Indicates successful Response
+ /// result : Result
+ /// ]
+ /// For error responses:
+ /// ResponsePacketError = [
+ /// error_code: ErrorCode, ; Indicate the error
+ /// error_message: tstr ; Additional human-readable context
+ /// ]
+ /// ```
+ /// See ResponsePacket<Result> in SecretManagement.cddl alongside ISecretkeeper.aidl
+ fn new(response_cbor: Vec<Value>) -> Result<Box<Self>, Error>;
+
+ /// The result in the `Response`. By default this is empty, but [`Response`] structures like
+ /// `GetVersionResponse` must overwrite these to return the expected non-empty result.
+ fn result(&self) -> Vec<Value> {
+ Vec::new()
+ }
+
+ /// Error code corresponding to the response. The default value is 0 but that will work only
+ /// for successful responses. Error-like response structures must overwrite this method.
+ fn error_code(&self) -> u16 {
+ ERROR_OK // Indicates success
+ }
+
+ /// Serialize the response to a [`ResponsePacket`].
+ fn serialize_to_packet(&self) -> ResponsePacket {
+ let mut res = self.result();
+ res.insert(0, Value::from(self.error_code()));
+ ResponsePacket::from(res)
+ }
+
+ /// Construct the response struct from given [`ResponsePacket`].
+ fn deserialize_from_packet(packet: ResponsePacket) -> Result<Box<Self>, Error> {
+ let res = packet.into_inner();
+ // Empty response packet is not allowed, all responses in Secretkeeper HAL at least
+ // have `error_code` or '0'; so throw an error!
+ if res.is_empty() {
+ return Err(Error::ResponseMalformed);
+ }
+ Self::new(res)
+ }
+}
diff --git a/secretkeeper/comm/src/lib.rs b/secretkeeper/comm/src/lib.rs
new file mode 100644
index 0000000..9a10ac0
--- /dev/null
+++ b/secretkeeper/comm/src/lib.rs
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! This library exposes data structures and methods that can be used by Secretkeeper HAL & client
+//! implementation. This is compatible with Secretkeeper HAL specification.
+
+#![no_std]
+extern crate alloc;
+
+mod cbor_convert;
+pub mod data_types;
diff --git a/secretkeeper/comm/tests/data_types.rs b/secretkeeper/comm/tests/data_types.rs
new file mode 100644
index 0000000..68964fd
--- /dev/null
+++ b/secretkeeper/comm/tests/data_types.rs
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 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.
+ */
+
+//! Unit tests for testing serialization & deserialization of exported data_types.
+
+use ciborium::Value;
+use secretkeeper_comm::data_types::error::{Error, SecretkeeperError, ERROR_OK};
+use secretkeeper_comm::data_types::packet::{RequestPacket, ResponsePacket, ResponseType};
+use secretkeeper_comm::data_types::request::Request;
+use secretkeeper_comm::data_types::request_response_impl::Opcode;
+use secretkeeper_comm::data_types::request_response_impl::{GetVersionRequest, GetVersionResponse};
+use secretkeeper_comm::data_types::response::Response;
+
+#[cfg(test)]
+rdroidtest::test_main!();
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use rdroidtest::test;
+
+ test!(request_serialization_deserialization);
+ fn request_serialization_deserialization() {
+ let req = GetVersionRequest {};
+ let packet = req.serialize_to_packet();
+ assert_eq!(packet.opcode().unwrap(), Opcode::GetVersion);
+ assert_eq!(
+ RequestPacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+ packet
+ );
+ let req_deserialized = *GetVersionRequest::deserialize_from_packet(packet).unwrap();
+ assert_eq!(req, req_deserialized);
+ }
+
+ test!(success_response_serialization_deserialization);
+ fn success_response_serialization_deserialization() {
+ let response = GetVersionResponse::new(1);
+ let packet = response.serialize_to_packet();
+ assert_eq!(packet.response_type().unwrap(), ResponseType::Success);
+ assert_eq!(
+ ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+ packet
+ );
+ let response_deserialized = *GetVersionResponse::deserialize_from_packet(packet).unwrap();
+ assert_eq!(response, response_deserialized);
+ }
+
+ test!(error_response_serialization_deserialization);
+ fn error_response_serialization_deserialization() {
+ let response = SecretkeeperError::RequestMalformed;
+ let packet = response.serialize_to_packet();
+ assert_eq!(packet.response_type().unwrap(), ResponseType::Error);
+ assert_eq!(
+ ResponsePacket::from_bytes(&packet.clone().into_bytes().unwrap()).unwrap(),
+ packet
+ );
+ let response_deserialized = *SecretkeeperError::deserialize_from_packet(packet).unwrap();
+ assert_eq!(response, response_deserialized);
+ }
+
+ test!(request_creation);
+ fn request_creation() {
+ let req: GetVersionRequest = *Request::new(vec![]).unwrap();
+ assert_eq!(req, GetVersionRequest {});
+ }
+
+ test!(response_creation);
+ fn response_creation() {
+ let res: GetVersionResponse =
+ *Response::new(vec![Value::from(ERROR_OK), Value::from(5)]).unwrap();
+ assert_eq!(res.version(), 5);
+ }
+
+ test!(invalid_get_version_request_creation);
+ fn invalid_get_version_request_creation() {
+ // A request with non-zero arg is considered invalid.
+ assert_eq!(
+ <GetVersionRequest as Request>::new(vec![Value::Null]).unwrap_err(),
+ Error::RequestMalformed
+ );
+ }
+
+ test!(invalid_get_version_response_creation);
+ fn invalid_get_version_response_creation() {
+ // A response with non-zero error_code is an invalid success response.
+ assert_eq!(
+ <GetVersionResponse as Response>::new(vec![
+ Value::from(SecretkeeperError::RequestMalformed as u16),
+ Value::from(5)
+ ])
+ .unwrap_err(),
+ Error::ResponseMalformed
+ );
+
+ // A response with incorrect size of array is invalid.
+ assert_eq!(
+ <GetVersionResponse as Response>::new(vec![
+ Value::from(ERROR_OK),
+ Value::from(5),
+ Value::from(7)
+ ])
+ .unwrap_err(),
+ Error::ResponseMalformed
+ );
+
+ // A response with incorrect type is invalid.
+ <GetVersionResponse as Response>::new(vec![Value::from(ERROR_OK), Value::from("a tstr")])
+ .unwrap_err();
+ }
+
+ test!(invalid_error_response_creation);
+ fn invalid_error_response_creation() {
+ // A response with ERROR_OK(0) as the error_code is an invalid error response.
+ assert_eq!(
+ <SecretkeeperError as Response>::new(vec![Value::from(ERROR_OK)]).unwrap_err(),
+ Error::ResponseMalformed
+ );
+ }
+}
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
index 2e91305..076ba3b 100644
--- a/secretkeeper/dice_policy/src/lib.rs
+++ b/secretkeeper/dice_policy/src/lib.rs
@@ -213,10 +213,10 @@
ConstraintType::GreaterOrEqual => {
let value_in_node = value_in_node
.as_integer()
- .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+ .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
let value_min = value_in_constraint
.as_integer()
- .ok_or(anyhow!("Mismatch type: expected a cbor integer"))?;
+ .ok_or(anyhow!("Mismatch type: expected a CBOR integer"))?;
ensure!(value_in_node >= value_min);
}
};
@@ -260,9 +260,9 @@
Value::Bytes(b) => value_from_bytes(b)?
.into_map()
.map(Cow::Owned)
- .map_err(|e| anyhow!("Expected a cbor map: {:?}", e)),
+ .map_err(|e| anyhow!("Expected a CBOR map: {:?}", e)),
Value::Map(map) => Ok(Cow::Borrowed(map)),
- _ => bail!("/Expected a cbor map {:?}", cbor_map),
+ _ => bail!("Expected a CBOR map {:?}", cbor_map),
}
}
diff --git a/service_vm/comm/Android.bp b/service_vm/comm/Android.bp
index 6e05587..bdfc099 100644
--- a/service_vm/comm/Android.bp
+++ b/service_vm/comm/Android.bp
@@ -24,6 +24,7 @@
"libbssl_avf_error_nostd",
"libciborium_nostd",
"libcoset_nostd",
+ "libder_nostd",
"liblog_rust_nostd",
"libserde_nostd",
],
diff --git a/service_vm/comm/src/message.rs b/service_vm/comm/src/message.rs
index 6dd0ccd..87c8378 100644
--- a/service_vm/comm/src/message.rs
+++ b/service_vm/comm/src/message.rs
@@ -66,6 +66,13 @@
/// The key blob retrieved from RKPD by virtualizationservice.
pub remotely_provisioned_key_blob: Vec<u8>,
+
+ /// The leaf certificate of the certificate chain retrieved from RKPD by
+ /// virtualizationservice.
+ ///
+ /// This certificate is a DER-encoded X.509 certificate that includes the remotely
+ /// provisioned public key.
+ pub remotely_provisioned_cert: Vec<u8>,
}
/// Represents a response to a request sent to the service VM.
@@ -120,6 +127,9 @@
/// The requested operation has not been implemented.
OperationUnimplemented,
+
+ /// An error happened during the DER encoding/decoding.
+ DerError,
}
impl fmt::Display for RequestProcessingError {
@@ -142,6 +152,9 @@
Self::OperationUnimplemented => {
write!(f, "The requested operation has not been implemented")
}
+ Self::DerError => {
+ write!(f, "An error happened during the DER encoding/decoding")
+ }
}
}
}
@@ -166,6 +179,14 @@
}
}
+#[cfg(not(feature = "std"))]
+impl From<der::Error> for RequestProcessingError {
+ fn from(e: der::Error) -> Self {
+ error!("DER encoding/decoding error: {e}");
+ Self::DerError
+ }
+}
+
/// Represents the params passed to GenerateCertificateRequest
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct GenerateCertificateRequestParams {
diff --git a/service_vm/requests/Android.bp b/service_vm/requests/Android.bp
index ecede8b..52b54b4 100644
--- a/service_vm/requests/Android.bp
+++ b/service_vm/requests/Android.bp
@@ -21,10 +21,13 @@
"libcbor_util_nostd",
"libciborium_nostd",
"libcoset_nostd",
+ "libder_nostd",
"libdiced_open_dice_nostd",
"liblog_rust_nostd",
"libserde_nostd",
"libservice_vm_comm_nostd",
+ "libspki_nostd",
+ "libx509_cert_nostd",
"libzeroize_nostd",
],
}
diff --git a/service_vm/requests/src/cert.rs b/service_vm/requests/src/cert.rs
new file mode 100644
index 0000000..68ca382
--- /dev/null
+++ b/service_vm/requests/src/cert.rs
@@ -0,0 +1,130 @@
+// 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.
+
+//! Generation of certificates and attestation extensions.
+
+use alloc::vec;
+use der::{
+ asn1::{BitStringRef, ObjectIdentifier, UIntRef},
+ oid::AssociatedOid,
+ Decode, Sequence,
+};
+use spki::{AlgorithmIdentifier, SubjectPublicKeyInfo};
+use x509_cert::{
+ certificate::{Certificate, TbsCertificate, Version},
+ ext::Extension,
+ name::Name,
+ time::Validity,
+};
+
+/// OID value for ECDSA with SHA-256, see RFC 5912 s6.
+const ECDSA_WITH_SHA_256: ObjectIdentifier = ObjectIdentifier::new_unwrap("1.2.840.10045.4.3.2");
+
+/// OID value for the protected VM remote attestation extension.
+///
+/// This OID value was added at cl/584542390.
+const AVF_ATTESTATION_EXTENSION_V1: ObjectIdentifier =
+ ObjectIdentifier::new_unwrap("1.3.6.1.4.1.11129.2.1.29.1");
+
+/// Attestation extension contents
+///
+/// ```asn1
+/// AttestationDescription ::= SEQUENCE {
+/// attestationChallenge OCTET_STRING,
+/// }
+/// ```
+/// TODO(b/312448064): Add VM root of trust and payload information to the extension.
+#[derive(Debug, Clone, Sequence)]
+pub(crate) struct AttestationExtension<'a> {
+ #[asn1(type = "OCTET STRING")]
+ attestation_challenge: &'a [u8],
+}
+
+impl<'a> AssociatedOid for AttestationExtension<'a> {
+ const OID: ObjectIdentifier = AVF_ATTESTATION_EXTENSION_V1;
+}
+
+impl<'a> AttestationExtension<'a> {
+ pub(crate) fn new(challenge: &'a [u8]) -> Self {
+ Self { attestation_challenge: challenge }
+ }
+}
+
+/// Builds an X.509 `Certificate` as defined in RFC 5280 Section 4.1:
+///
+/// ```asn1
+/// Certificate ::= SEQUENCE {
+/// tbsCertificate TBSCertificate,
+/// signatureAlgorithm AlgorithmIdentifier,
+/// signature BIT STRING
+/// }
+/// ```
+pub(crate) fn build_certificate<'a>(
+ tbs_cert: TbsCertificate<'a>,
+ signature: &'a [u8],
+) -> der::Result<Certificate<'a>> {
+ Ok(Certificate {
+ signature_algorithm: tbs_cert.signature,
+ tbs_certificate: tbs_cert,
+ signature: BitStringRef::new(0, signature)?,
+ })
+}
+
+/// Builds an X.509 `TbsCertificate` as defined in RFC 5280 Section 4.1:
+///
+/// ```asn1
+/// TBSCertificate ::= SEQUENCE {
+/// version [0] EXPLICIT Version DEFAULT v1,
+/// serialNumber CertificateSerialNumber,
+/// signature AlgorithmIdentifier,
+/// issuer Name,
+/// validity Validity,
+/// subject Name,
+/// subjectPublicKeyInfo SubjectPublicKeyInfo,
+/// issuerUniqueID [1] IMPLICIT UniqueIdentifier OPTIONAL,
+/// -- If present, version MUST be v2 or v3
+/// subjectUniqueID [2] IMPLICIT UniqueIdentifier OPTIONAL,
+/// -- If present, version MUST be v2 or v3
+/// extensions [3] Extensions OPTIONAL
+/// -- If present, version MUST be v3 --
+/// }
+/// ```
+pub(crate) fn build_tbs_certificate<'a>(
+ serial_number: &'a [u8],
+ issuer: Name<'a>,
+ subject: Name<'a>,
+ validity: Validity,
+ subject_public_key_info: &'a [u8],
+ attestation_ext: &'a [u8],
+) -> der::Result<TbsCertificate<'a>> {
+ 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,
+ }];
+ Ok(TbsCertificate {
+ version: Version::V3,
+ serial_number: UIntRef::new(serial_number)?,
+ signature,
+ issuer,
+ validity,
+ subject,
+ subject_public_key_info,
+ issuer_unique_id: None,
+ subject_unique_id: None,
+ extensions: Some(extensions),
+ })
+}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 612605f..e1f345c 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -15,14 +15,17 @@
//! This module contains functions related to the attestation of the
//! client VM.
+use crate::cert;
use crate::keyblob::decrypt_private_key;
use alloc::vec::Vec;
-use bssl_avf::{sha256, EcKey};
+use bssl_avf::{rand_bytes, sha256, EcKey, EvpPKey};
use core::result;
use coset::{CborSerializable, CoseSign};
+use der::{Decode, Encode};
use diced_open_dice::DiceArtifacts;
use log::error;
use service_vm_comm::{ClientVmAttestationParams, Csr, CsrPayload, RequestProcessingError};
+use x509_cert::{certificate::Certificate, name::Name};
type Result<T> = result::Result<T, RequestProcessingError>;
@@ -50,21 +53,41 @@
cose_sign.verify_signature(ATTESTATION_KEY_SIGNATURE_INDEX, aad, |signature, message| {
ecdsa_verify(&ec_public_key, signature, message)
})?;
+ let subject_public_key_info = EvpPKey::try_from(ec_public_key)?.subject_public_key_info()?;
// TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
// cert with RKP VM's DICE chain.
+ // Builds the TBSCertificate.
+ // The serial number can be up to 20 bytes according to RFC5280 s4.1.2.2.
+ // In this case, a serial number with a length of 20 bytes is used to ensure that each
+ // certificate signed by RKP VM has a unique serial number.
+ let mut serial_number = [0u8; 20];
+ rand_bytes(&mut serial_number)?;
+ let subject = Name::encode_from_string("CN=Android Protected Virtual Machine Key")?;
+ let rkp_cert = Certificate::from_der(¶ms.remotely_provisioned_cert)?;
+ let attestation_ext = cert::AttestationExtension::new(&csr_payload.challenge).to_vec()?;
+ let tbs_cert = cert::build_tbs_certificate(
+ &serial_number,
+ rkp_cert.tbs_certificate.subject,
+ Name::from_der(&subject)?,
+ rkp_cert.tbs_certificate.validity,
+ &subject_public_key_info,
+ &attestation_ext,
+ )?;
+
+ // Signs the TBSCertificate and builds the Certificate.
+ // The two private key structs below will be zeroed out on drop.
let private_key =
decrypt_private_key(¶ms.remotely_provisioned_key_blob, dice_artifacts.cdi_seal())
.map_err(|e| {
error!("Failed to decrypt the remotely provisioned key blob: {e}");
RequestProcessingError::FailedToDecryptKeyBlob
})?;
- let _ec_private_key = EcKey::from_ec_private_key(private_key.as_slice())?;
-
- // TODO(b/309441500): Build a new certificate signed with the remotely provisioned
- // `_private_key`.
- Err(RequestProcessingError::OperationUnimplemented)
+ 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 certificate = cert::build_certificate(tbs_cert, &signature)?;
+ Ok(certificate.to_vec()?)
}
fn ecdsa_verify(key: &EcKey, signature: &[u8], message: &[u8]) -> bssl_avf::Result<()> {
@@ -72,3 +95,8 @@
let digest = sha256(message)?;
key.ecdsa_verify(signature, &digest)
}
+
+fn ecdsa_sign(key: &EcKey, message: &[u8]) -> bssl_avf::Result<Vec<u8>> {
+ let digest = sha256(message)?;
+ key.ecdsa_sign(&digest)
+}
diff --git a/service_vm/requests/src/lib.rs b/service_vm/requests/src/lib.rs
index b2db298..3f687a4 100644
--- a/service_vm/requests/src/lib.rs
+++ b/service_vm/requests/src/lib.rs
@@ -19,6 +19,7 @@
extern crate alloc;
mod api;
+mod cert;
mod client_vm;
mod keyblob;
mod pub_key;
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index 2c01451..9901a92 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -76,10 +76,13 @@
public_keys.push(public_key.to_cbor_value()?);
}
// Builds `CsrPayload`.
+ // TODO(b/299256925): The device information is currently empty as we do not
+ // have sufficient details to include.
+ let device_info = Value::Map(Vec::new());
let csr_payload = cbor!([
Value::Integer(CSR_PAYLOAD_SCHEMA_V3.into()),
Value::Text(String::from(CERTIFICATE_TYPE)),
- // TODO(b/299256925): Add device info in CBOR format here.
+ device_info,
Value::Array(public_keys),
])?;
let csr_payload = cbor_util::serialize(&csr_payload)?;
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 001dfeb..886ca81 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -450,7 +450,9 @@
key, keyOverrides, /* isProtected= */ false, /* updateBootconfigs= */ true);
assertThatEventually(
100000,
- () -> getDevice().pullFileContents(LOG_PATH),
+ () ->
+ getDevice().pullFileContents(CONSOLE_PATH)
+ + getDevice().pullFileContents(LOG_PATH),
containsString("boot completed, time to run payload"));
vmInfo.mProcess.destroy();
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index c6291e4..b06eea6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -1293,7 +1293,7 @@
assertThat(payloadStarted.getNow(false)).isTrue();
assertThat(exitCodeFuture.getNow(0)).isNotEqualTo(0);
- assertThat(listener.getLogOutput()).contains(reason);
+ assertThat(listener.getConsoleOutput() + listener.getLogOutput()).contains(reason);
}
@Test
@@ -1698,7 +1698,7 @@
.command(
"logcat",
"-e",
- "virtualizationmanager::aidl: Log.*executing main task",
+ "virtualizationmanager::aidl: (Console|Log).*executing main task",
"-t",
time)
.start();
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 6ae3bbd..c63ed4c 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -66,6 +66,7 @@
use log::{debug, error, info, warn};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::unistd::pipe;
+use regex::Regex;
use rpcbinder::RpcServer;
use rustutils::system_properties;
use semver::VersionReq;
@@ -101,8 +102,6 @@
const MICRODROID_OS_NAME: &str = "microdroid";
-const MICRODROID_GKI_OS_NAME: &str = "microdroid_gki";
-
const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
/// Roughly estimated sufficient size for storing vendor public key into DTBO.
@@ -115,6 +114,8 @@
pub static ref GLOBAL_SERVICE: Strong<dyn IVirtualizationServiceInternal> =
wait_for_interface(BINDER_SERVICE_IDENTIFIER)
.expect("Could not connect to VirtualizationServiceInternal");
+ static ref MICRODROID_GKI_OS_NAME_PATTERN: Regex =
+ Regex::new(r"^microdroid_gki-\d+\.\d+$").expect("Failed to construct Regex");
}
fn create_or_update_idsig_file(
@@ -482,7 +483,7 @@
}
};
- let vfio_devices = if !config.devices.is_empty() {
+ let (vfio_devices, dtbo) = if !config.devices.is_empty() {
let mut set = HashSet::new();
for device in config.devices.iter() {
let path = canonicalize(device)
@@ -493,16 +494,25 @@
.or_binder_exception(ExceptionCode::ILLEGAL_ARGUMENT);
}
}
- GLOBAL_SERVICE
+ let devices = GLOBAL_SERVICE
.bindDevicesToVfioDriver(&config.devices)?
.into_iter()
.map(|x| VfioDevice {
sysfs_path: PathBuf::from(&x.sysfsPath),
dtbo_label: x.dtboLabel,
})
- .collect::<Vec<_>>()
+ .collect::<Vec<_>>();
+ let dtbo_file = File::from(
+ GLOBAL_SERVICE
+ .getDtboFile()?
+ .as_ref()
+ .try_clone()
+ .context("Failed to create File from ParcelFileDescriptor")
+ .or_binder_exception(ExceptionCode::BAD_PARCELABLE)?,
+ );
+ (devices, Some(dtbo_file))
} else {
- vec![]
+ (vec![], None)
};
// Actually start the VM.
@@ -529,6 +539,7 @@
detect_hangup: is_app_config,
gdb_port,
vfio_devices,
+ dtbo,
dtbo_vendor,
};
let instance = Arc::new(
@@ -698,12 +709,12 @@
fn is_valid_os(os_name: &str) -> bool {
if os_name == MICRODROID_OS_NAME {
- return true;
+ true
+ } else if cfg!(vendor_modules) && MICRODROID_GKI_OS_NAME_PATTERN.is_match(os_name) {
+ PathBuf::from(format!("/apex/com.android.virt/etc/{}.json", os_name)).exists()
+ } else {
+ false
}
- if cfg!(vendor_modules) && os_name == MICRODROID_GKI_OS_NAME {
- return true;
- }
- false
}
fn load_app_config(
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index 9a50776..2ba0e0e 100644
--- a/virtualizationmanager/src/crosvm.rs
+++ b/virtualizationmanager/src/crosvm.rs
@@ -116,6 +116,7 @@
pub detect_hangup: bool,
pub gdb_port: Option<NonZeroU16>,
pub vfio_devices: Vec<VfioDevice>,
+ pub dtbo: Option<File>,
pub dtbo_vendor: Option<File>,
}
@@ -723,11 +724,23 @@
}
}
-fn append_platform_devices(command: &mut Command, config: &CrosvmConfig) -> Result<(), Error> {
+fn append_platform_devices(
+ command: &mut Command,
+ preserved_fds: &mut Vec<RawFd>,
+ config: &CrosvmConfig,
+) -> Result<(), Error> {
+ if config.vfio_devices.is_empty() {
+ return Ok(());
+ }
+
+ let Some(dtbo) = &config.dtbo else {
+ bail!("VFIO devices assigned but no DTBO available");
+ };
+ command.arg(format!("--device-tree-overlay={},filter", add_preserved_fd(preserved_fds, dtbo)));
+
for device in &config.vfio_devices {
command.arg(vfio_argument_for_platform_device(device)?);
}
- // TODO(b/291192693): add dtbo to command line when assigned device is not empty.
Ok(())
}
@@ -889,7 +902,7 @@
// TODO(b/285855436): Pass dtbo_vendor after --device-tree-overlay crosvm option is supported.
- append_platform_devices(&mut command, &config)?;
+ append_platform_devices(&mut command, &mut preserved_fds, &config)?;
debug!("Preserving FDs {:?}", preserved_fds);
command.preserved_fds(preserved_fds);
diff --git a/virtualizationservice/Android.bp b/virtualizationservice/Android.bp
index bef7dd0..3f8d193 100644
--- a/virtualizationservice/Android.bp
+++ b/virtualizationservice/Android.bp
@@ -2,8 +2,8 @@
default_applicable_licenses: ["Android-Apache-2.0"],
}
-rust_binary {
- name: "virtualizationservice",
+rust_defaults {
+ name: "virtualizationservice_defaults",
crate_name: "virtualizationservice",
defaults: ["avf_build_flags_rust"],
edition: "2021",
@@ -31,6 +31,7 @@
"libanyhow",
"libavflog",
"libbinder_rs",
+ "libhypervisor_props",
"liblibc",
"liblog_rust",
"libnix",
@@ -44,13 +45,39 @@
"libserde_xml_rs",
"libservice_vm_comm",
"libservice_vm_manager",
+ "libx509_parser",
],
apex_available: ["com.android.virt"],
}
+rust_binary {
+ name: "virtualizationservice",
+ defaults: ["virtualizationservice_defaults"],
+}
+
xsd_config {
name: "assignable_devices",
srcs: ["assignable_devices.xsd"],
api_dir: "schema",
package_name: "android.system.virtualizationservice",
}
+
+rust_test {
+ name: "virtualizationservice_test",
+ defaults: ["virtualizationservice_defaults"],
+ test_suites: ["general-tests"],
+ data: [
+ ":test_rkp_cert_chain",
+ ],
+}
+
+// The chain originates from a CTS test for Keymint, with the Keymint certificate
+// (leaf certificate) truncated.
+//
+// The certificate chain begins with a leaf certificate obtained from RKP and ends
+// with a root certificate. Each certificate in the chain possesses a signature that
+// is signed by the private key of the subsequent certificate in the chain.
+filegroup {
+ name: "test_rkp_cert_chain",
+ srcs: ["testdata/rkp_cert_chain.der"],
+}
diff --git a/virtualizationservice/TEST_MAPPING b/virtualizationservice/TEST_MAPPING
new file mode 100644
index 0000000..4fef83c
--- /dev/null
+++ b/virtualizationservice/TEST_MAPPING
@@ -0,0 +1,9 @@
+// When adding or removing tests here, don't forget to amend _all_modules list in
+// wireless/android/busytown/ath_config/configs/prod/avf/tests.gcl
+{
+ "avf-presubmit" : [
+ {
+ "name" : "virtualizationservice_test"
+ }
+ ]
+}
diff --git a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
index c384a6f..a2cb693 100644
--- a/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
+++ b/virtualizationservice/aidl/android/system/virtualizationservice_internal/IVirtualizationServiceInternal.aidl
@@ -81,4 +81,7 @@
* @return a list of pairs (sysfs path, DTBO node label) for devices.
*/
BoundDevice[] bindDevicesToVfioDriver(in String[] devices);
+
+ /** Returns a read-only file descriptor of the VM DTBO file. */
+ ParcelFileDescriptor getDtboFile();
}
diff --git a/virtualizationservice/src/aidl.rs b/virtualizationservice/src/aidl.rs
index 938225e..3ac1e60 100644
--- a/virtualizationservice/src/aidl.rs
+++ b/virtualizationservice/src/aidl.rs
@@ -43,7 +43,7 @@
use rustutils::system_properties;
use serde::Deserialize;
use std::collections::{HashMap, HashSet};
-use std::fs::{self, create_dir, remove_dir_all, set_permissions, File, Permissions};
+use std::fs::{self, create_dir, remove_dir_all, remove_file, set_permissions, File, Permissions};
use std::io::{Read, Write};
use std::os::unix::fs::PermissionsExt;
use std::os::unix::raw::{pid_t, uid_t};
@@ -52,6 +52,7 @@
use tombstoned_client::{DebuggerdDumpType, TombstonedConnection};
use vsock::{VsockListener, VsockStream};
use nix::unistd::{chown, Uid};
+use x509_parser::{traits::FromDer, certificate::X509Certificate};
/// The unique ID of a VM used (together with a port number) for vsock communication.
pub type Cid = u32;
@@ -166,35 +167,46 @@
requester_uid: i32,
) -> binder::Result<Vec<Certificate>> {
check_manage_access()?;
- info!("Received csr. Requestting attestation...");
- if cfg!(remote_attestation) {
- let attestation_key = get_rkpd_attestation_key(
- REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
- requester_uid as u32,
- )
- .context("Failed to retrieve the remotely provisioned keys")
- .with_log()
- .or_service_specific_exception(-1)?;
- let certificate = request_attestation(csr, &attestation_key.keyBlob)
- .context("Failed to request attestation")
- .with_log()
- .or_service_specific_exception(-1)?;
- // TODO(b/309780089): Parse the remotely provisioned certificate chain into
- // individual certificates.
- let mut certificate_chain =
- vec![Certificate { encodedCertificate: attestation_key.encodedCertChain }];
- certificate_chain.push(Certificate { encodedCertificate: certificate });
- Ok(certificate_chain)
- } else {
- Err(Status::new_exception_str(
+ if !cfg!(remote_attestation) {
+ return Err(Status::new_exception_str(
ExceptionCode::UNSUPPORTED_OPERATION,
Some(
"requestAttestation is not supported with the remote_attestation feature \
disabled",
),
))
- .with_log()
+ .with_log();
}
+ info!("Received csr. Requestting attestation...");
+ let attestation_key = get_rkpd_attestation_key(
+ REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+ requester_uid as u32,
+ )
+ .context("Failed to retrieve the remotely provisioned keys")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ let mut certificate_chain = split_x509_certificate_chain(&attestation_key.encodedCertChain)
+ .context("Failed to split the remotely provisioned certificate chain")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ if certificate_chain.is_empty() {
+ return Err(Status::new_service_specific_error_str(
+ -1,
+ Some("The certificate chain should contain at least 1 certificate"),
+ ))
+ .with_log();
+ }
+ let certificate = request_attestation(
+ csr.to_vec(),
+ attestation_key.keyBlob,
+ certificate_chain[0].encodedCertificate.clone(),
+ )
+ .context("Failed to request attestation")
+ .with_log()
+ .or_service_specific_exception(-1)?;
+ certificate_chain.insert(0, Certificate { encodedCertificate: certificate });
+
+ Ok(certificate_chain)
}
fn getAssignableDevices(&self) -> binder::Result<Vec<AssignableDevice>> {
@@ -212,18 +224,8 @@
let vfio_service: Strong<dyn IVfioHandler> =
wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())?;
-
vfio_service.bindDevicesToVfioDriver(devices)?;
- let dtbo_path = Path::new(TEMPORARY_DIRECTORY).join("common").join("dtbo");
- if !dtbo_path.exists() {
- // open a writable file descriptor for vfio_handler
- let dtbo = File::create(&dtbo_path)
- .context("Failed to create VM DTBO file")
- .or_service_specific_exception(-1)?;
- vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(dtbo))?;
- }
-
Ok(get_assignable_devices()?
.device
.into_iter()
@@ -236,6 +238,14 @@
})
.collect::<Vec<_>>())
}
+
+ fn getDtboFile(&self) -> binder::Result<ParcelFileDescriptor> {
+ check_use_custom_virtual_machine()?;
+
+ let state = &mut *self.state.lock().unwrap();
+ let file = state.get_dtbo_file().or_service_specific_exception(-1)?;
+ Ok(ParcelFileDescriptor::new(file))
+ }
}
// KEEP IN SYNC WITH assignable_devices.xsd
@@ -290,6 +300,17 @@
Ok(devices)
}
+fn split_x509_certificate_chain(mut cert_chain: &[u8]) -> Result<Vec<Certificate>> {
+ let mut out = Vec::new();
+ while !cert_chain.is_empty() {
+ let (remaining, _) = X509Certificate::from_der(cert_chain)?;
+ let end = cert_chain.len() - remaining.len();
+ out.push(Certificate { encodedCertificate: cert_chain[..end].to_vec() });
+ cert_chain = remaining;
+ }
+ Ok(out)
+}
+
#[derive(Debug, Default)]
struct GlobalVmInstance {
/// The unique CID assigned to the VM for vsock communication.
@@ -314,6 +335,9 @@
/// VM contexts currently allocated to running VMs. A CID is never recycled as long
/// as there is a strong reference held by a GlobalVmContext.
held_contexts: HashMap<Cid, Weak<GlobalVmInstance>>,
+
+ /// Cached read-only FD of VM DTBO file. Also serves as a lock for creating the file.
+ dtbo_file: Mutex<Option<File>>,
}
impl GlobalState {
@@ -377,26 +401,64 @@
let cid = self.get_next_available_cid()?;
let instance = Arc::new(GlobalVmInstance { cid, requester_uid, requester_debug_pid });
- create_temporary_directory(&instance.get_temp_dir(), requester_uid)?;
+ create_temporary_directory(&instance.get_temp_dir(), Some(requester_uid))?;
self.held_contexts.insert(cid, Arc::downgrade(&instance));
let binder = GlobalVmContext { instance, ..Default::default() };
Ok(BnGlobalVmContext::new_binder(binder, BinderFeatures::default()))
}
+
+ fn get_dtbo_file(&mut self) -> Result<File> {
+ let mut file = self.dtbo_file.lock().unwrap();
+
+ let fd = if let Some(ref_fd) = &*file {
+ ref_fd.try_clone()?
+ } else {
+ let path = get_or_create_common_dir()?.join("vm.dtbo");
+ if path.exists() {
+ // All temporary files are deleted when the service is started.
+ // If the file exists but the FD is not cached, the file is
+ // likely corrupted.
+ remove_file(&path).context("Failed to clone cached VM DTBO file descriptor")?;
+ }
+
+ // Open a write-only file descriptor for vfio_handler.
+ let write_fd = File::create(&path).context("Failed to create VM DTBO file")?;
+
+ let vfio_service: Strong<dyn IVfioHandler> =
+ wait_for_interface(<BpVfioHandler as IVfioHandler>::get_descriptor())?;
+ vfio_service.writeVmDtbo(&ParcelFileDescriptor::new(write_fd))?;
+
+ // Open read-only. This FD will be cached and returned to clients.
+ let read_fd = File::open(&path).context("Failed to open VM DTBO file")?;
+ let read_fd_clone =
+ read_fd.try_clone().context("Failed to clone VM DTBO file descriptor")?;
+ *file = Some(read_fd);
+ read_fd_clone
+ };
+
+ Ok(fd)
+ }
}
-fn create_temporary_directory(path: &PathBuf, requester_uid: uid_t) -> Result<()> {
+fn create_temporary_directory(path: &PathBuf, requester_uid: Option<uid_t>) -> Result<()> {
+ // Directory may exist if previous attempt to create it had failed.
+ // Delete it before trying again.
if path.as_path().exists() {
remove_temporary_dir(path).unwrap_or_else(|e| {
warn!("Could not delete temporary directory {:?}: {}", path, e);
});
}
- // Create a directory that is owned by client's UID but system's GID, and permissions 0700.
+ // Create directory.
+ create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
+ // If provided, change ownership to client's UID but system's GID, and permissions 0700.
// If the chown() fails, this will leave behind an empty directory that will get removed
// at the next attempt, or if virtualizationservice is restarted.
- create_dir(path).with_context(|| format!("Could not create temporary directory {:?}", path))?;
- chown(path, Some(Uid::from_raw(requester_uid)), None)
- .with_context(|| format!("Could not set ownership of temporary directory {:?}", path))?;
+ if let Some(uid) = requester_uid {
+ chown(path, Some(Uid::from_raw(uid)), None).with_context(|| {
+ format!("Could not set ownership of temporary directory {:?}", path)
+ })?;
+ }
Ok(())
}
@@ -410,6 +472,14 @@
Ok(())
}
+fn get_or_create_common_dir() -> Result<PathBuf> {
+ let path = Path::new(TEMPORARY_DIRECTORY).join("common");
+ if !path.exists() {
+ create_temporary_directory(&path, None)?;
+ }
+ Ok(path)
+}
+
/// Implementation of the AIDL `IGlobalVmContext` interface.
#[derive(Debug, Default)]
struct GlobalVmContext {
@@ -514,3 +584,24 @@
fn check_use_custom_virtual_machine() -> binder::Result<()> {
check_permission("android.permission.USE_CUSTOM_VIRTUAL_MACHINE")
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use std::fs;
+
+ const TEST_RKP_CERT_CHAIN_PATH: &str = "testdata/rkp_cert_chain.der";
+
+ #[test]
+ fn splitting_x509_certificate_chain_succeeds() -> Result<()> {
+ let bytes = fs::read(TEST_RKP_CERT_CHAIN_PATH)?;
+ let cert_chain = split_x509_certificate_chain(&bytes)?;
+
+ assert_eq!(4, cert_chain.len());
+ for cert in cert_chain {
+ let (remaining, _) = X509Certificate::from_der(&cert.encodedCertificate)?;
+ assert!(remaining.is_empty());
+ }
+ Ok(())
+ }
+}
diff --git a/virtualizationservice/src/main.rs b/virtualizationservice/src/main.rs
index d80ddd4..ea073bf 100644
--- a/virtualizationservice/src/main.rs
+++ b/virtualizationservice/src/main.rs
@@ -34,7 +34,7 @@
const LOG_TAG: &str = "VirtualizationService";
pub(crate) const REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME: &str =
- "android.system.virtualization.IRemotelyProvisionedComponent/avf";
+ "android.hardware.security.keymint.IRemotelyProvisionedComponent/avf";
fn get_calling_pid() -> pid_t {
ThreadState::get_calling_pid()
@@ -69,10 +69,17 @@
register_lazy_service(BINDER_SERVICE_IDENTIFIER, service.as_binder()).unwrap();
info!("Registered Binder service {}.", BINDER_SERVICE_IDENTIFIER);
- // The IRemotelyProvisionedComponent service is only supposed to be triggered by rkpd for
- // RKP VM attestation.
- let _remote_provisioning_service = remote_provisioning::new_binder();
- // TODO(b/274881098): Register the RKP service when the implementation is ready.
+ if cfg!(remote_attestation) {
+ // The IRemotelyProvisionedComponent service is only supposed to be triggered by rkpd for
+ // RKP VM attestation.
+ let remote_provisioning_service = remote_provisioning::new_binder();
+ register_lazy_service(
+ REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME,
+ remote_provisioning_service.as_binder(),
+ )
+ .unwrap();
+ info!("Registered Binder service {}.", REMOTELY_PROVISIONED_COMPONENT_SERVICE_NAME);
+ }
ProcessState::join_thread_pool();
}
diff --git a/virtualizationservice/src/remote_provisioning.rs b/virtualizationservice/src/remote_provisioning.rs
index a9a07a5..40f54db 100644
--- a/virtualizationservice/src/remote_provisioning.rs
+++ b/virtualizationservice/src/remote_provisioning.rs
@@ -27,7 +27,11 @@
};
use anyhow::Context;
use avflog::LogResult;
-use binder::{BinderFeatures, Interface, IntoBinderResult, Result as BinderResult, Status, Strong};
+use binder::{
+ BinderFeatures, ExceptionCode, Interface, IntoBinderResult, Result as BinderResult, Status,
+ Strong,
+};
+use hypervisor_props::is_protected_vm_supported;
use service_vm_comm::{RequestProcessingError, Response};
/// Constructs a binder object that implements `IRemotelyProvisionedComponent`.
@@ -45,11 +49,13 @@
#[allow(non_snake_case)]
impl IRemotelyProvisionedComponent for AvfRemotelyProvisionedComponent {
fn getHardwareInfo(&self) -> BinderResult<RpcHardwareInfo> {
+ check_protected_vm_is_supported()?;
+
Ok(RpcHardwareInfo {
versionNumber: 3,
rpcAuthorName: String::from("Android Virtualization Framework"),
supportedEekCurve: CURVE_NONE,
- uniqueId: Some(String::from("Android Virtualization Framework 1")),
+ uniqueId: Some(String::from("AVF Remote Provisioning 1")),
supportedNumKeysInCsr: MIN_SUPPORTED_NUM_KEYS_IN_CSR,
})
}
@@ -59,6 +65,8 @@
testMode: bool,
macedPublicKey: &mut MacedPublicKey,
) -> BinderResult<Vec<u8>> {
+ check_protected_vm_is_supported()?;
+
if testMode {
return Err(Status::new_service_specific_error_str(
STATUS_REMOVED,
@@ -101,6 +109,8 @@
keysToSign: &[MacedPublicKey],
challenge: &[u8],
) -> BinderResult<Vec<u8>> {
+ check_protected_vm_is_supported()?;
+
const MAX_CHALLENGE_SIZE: usize = 64;
if challenge.len() > MAX_CHALLENGE_SIZE {
let message = format!(
@@ -123,6 +133,18 @@
}
}
+fn check_protected_vm_is_supported() -> BinderResult<()> {
+ if is_protected_vm_supported().unwrap_or(false) {
+ Ok(())
+ } else {
+ Err(Status::new_exception_str(
+ ExceptionCode::UNSUPPORTED_OPERATION,
+ Some("Protected VM support is missing for this operation"),
+ ))
+ .with_log()
+ }
+}
+
fn to_service_specific_error(response: Response) -> Status {
match response {
Response::Err(e) => match e {
diff --git a/virtualizationservice/src/rkpvm.rs b/virtualizationservice/src/rkpvm.rs
index 5087120..79e09b0 100644
--- a/virtualizationservice/src/rkpvm.rs
+++ b/virtualizationservice/src/rkpvm.rs
@@ -24,15 +24,14 @@
use service_vm_manager::ServiceVm;
pub(crate) fn request_attestation(
- csr: &[u8],
- remotely_provisioned_keyblob: &[u8],
+ csr: Vec<u8>,
+ remotely_provisioned_key_blob: Vec<u8>,
+ remotely_provisioned_cert: Vec<u8>,
) -> Result<Vec<u8>> {
let mut vm = ServiceVm::start()?;
- let params = ClientVmAttestationParams {
- csr: csr.to_vec(),
- remotely_provisioned_key_blob: remotely_provisioned_keyblob.to_vec(),
- };
+ let params =
+ ClientVmAttestationParams { csr, remotely_provisioned_key_blob, remotely_provisioned_cert };
let request = Request::RequestClientVmAttestation(params);
match vm.process_request(request).context("Failed to process request")? {
Response::RequestClientVmAttestation(cert) => Ok(cert),
diff --git a/virtualizationservice/testdata/rkp_cert_chain.der b/virtualizationservice/testdata/rkp_cert_chain.der
new file mode 100644
index 0000000..f32065d
--- /dev/null
+++ b/virtualizationservice/testdata/rkp_cert_chain.der
Binary files differ
diff --git a/vm/src/main.rs b/vm/src/main.rs
index 87278bc..9a92f13 100644
--- a/vm/src/main.rs
+++ b/vm/src/main.rs
@@ -27,6 +27,7 @@
use clap::{Args, Parser};
use create_idsig::command_create_idsig;
use create_partition::command_create_partition;
+use glob::glob;
use run::{command_run, command_run_app, command_run_microdroid};
use std::num::NonZeroU16;
use std::path::{Path, PathBuf};
@@ -107,10 +108,10 @@
#[arg(long)]
devices: Vec<PathBuf>,
- /// If set, use GKI instead of microdroid kernel
+ /// Version of GKI to use. If set, use instead of microdroid kernel
#[cfg(vendor_modules)]
#[arg(long)]
- gki: bool,
+ gki: Option<String>,
}
impl MicrodroidConfig {
@@ -125,13 +126,13 @@
}
#[cfg(vendor_modules)]
- fn gki(&self) -> bool {
- self.gki
+ fn gki(&self) -> Option<&str> {
+ self.gki.as_deref()
}
#[cfg(not(vendor_modules))]
- fn gki(&self) -> bool {
- false
+ fn gki(&self) -> Option<&str> {
+ None
}
#[cfg(device_assignment)]
@@ -315,6 +316,12 @@
Ok(())
}
+fn extract_gki_version(gki_config: &Path) -> Option<&str> {
+ let name = gki_config.file_name()?;
+ let name_str = name.to_str()?;
+ name_str.strip_prefix("microdroid_gki-")?.strip_suffix(".json")
+}
+
/// Print information about supported VM types.
fn command_info() -> Result<(), Error> {
let non_protected_vm_supported = hypervisor_props::is_vm_supported()?;
@@ -354,6 +361,12 @@
let devices = devices.into_iter().map(|x| x.node).collect::<Vec<_>>();
println!("Assignable devices: {}", serde_json::to_string(&devices)?);
+ let gki_configs =
+ glob("/apex/com.android.virt/etc/microdroid_gki-*.json")?.collect::<Result<Vec<_>, _>>()?;
+ let gki_versions =
+ gki_configs.iter().filter_map(|x| extract_gki_version(x)).collect::<Vec<_>>();
+ println!("Available gki versions: {}", serde_json::to_string(&gki_versions)?);
+
Ok(())
}
diff --git a/vm/src/run.rs b/vm/src/run.rs
index 44ba9af..8721e71 100644
--- a/vm/src/run.rs
+++ b/vm/src/run.rs
@@ -111,8 +111,11 @@
}
Payload::ConfigPath(config_path)
} else if let Some(payload_binary_name) = config.payload_binary_name {
- let os_name =
- if config.microdroid.gki() { "microdroid_gki" } else { "microdroid" }.to_owned();
+ let os_name = if let Some(ver) = config.microdroid.gki() {
+ format!("microdroid_gki-{ver}")
+ } else {
+ "microdroid".to_owned()
+ };
Payload::PayloadConfig(VirtualMachinePayloadConfig {
payloadBinaryName: payload_binary_name,
osName: os_name,
diff --git a/vm_payload/include/vm_payload.h b/vm_payload/include/vm_payload.h
index 78cd80d..3483e1d 100644
--- a/vm_payload/include/vm_payload.h
+++ b/vm_payload/include/vm_payload.h
@@ -224,8 +224,8 @@
* Gets the number of certificates in the certificate chain.
*
* The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
- * the attestation key's certificate chain. It starts with a root certificate and ends with a
- * leaf certificate covering the attested public key.
+ * the attestation key's certificate chain. It starts with a leaf certificate covering the attested
+ * public key and ends with a root certificate.
*
* \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
* when the attestation succeeds.
@@ -240,8 +240,8 @@
* attestation result.
*
* The certificate chain consists of a sequence of DER-encoded X.509 certificates that form
- * the attestation key's certificate chain. It starts with a root certificate and ends with a
- * leaf certificate covering the attested public key.
+ * the attestation key's certificate chain. It starts with a leaf certificate covering the attested
+ * public key and ends with a root certificate.
*
* \param result A pointer to the attestation result obtained from `AVmPayload_requestAttestation`
* when the attestation succeeds.