Merge "Add support for multiple GKI versions on VM" 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 d3e736f..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",
@@ -148,6 +149,15 @@
"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/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 7e677c4..6436be3 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/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/libs/libfdt/Android.bp b/libs/libfdt/Android.bp
index 5920d5d..ba9e971 100644
--- a/libs/libfdt/Android.bp
+++ b/libs/libfdt/Android.bp
@@ -69,22 +69,34 @@
genrule {
name: "fdt_test_tree_one_memory_range_dtb",
- defaults: ["dts_to_dtb"],
- srcs: ["tests/data/test_tree_one_memory_range.dts"],
+ tools: ["dtc"],
+ srcs: [
+ "tests/data/test_tree_one_memory_range.dts",
+ "tests/data/test_tree_no_memory_node.dts",
+ ],
+ cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_one_memory_range.dts) -o $(out)",
out: ["data/test_tree_one_memory_range.dtb"],
}
genrule {
name: "fdt_test_tree_multiple_memory_ranges_dtb",
- defaults: ["dts_to_dtb"],
- srcs: ["tests/data/test_tree_multiple_memory_ranges.dts"],
+ tools: ["dtc"],
+ srcs: [
+ "tests/data/test_tree_multiple_memory_ranges.dts",
+ "tests/data/test_tree_no_memory_node.dts",
+ ],
+ cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_multiple_memory_ranges.dts) -o $(out)",
out: ["data/test_tree_multiple_memory_ranges.dtb"],
}
genrule {
name: "fdt_test_tree_empty_memory_range_dtb",
- defaults: ["dts_to_dtb"],
- srcs: ["tests/data/test_tree_empty_memory_range.dts"],
+ tools: ["dtc"],
+ srcs: [
+ "tests/data/test_tree_empty_memory_range.dts",
+ "tests/data/test_tree_no_memory_node.dts",
+ ],
+ cmd: "$(location dtc) -I dts -O dtb $(location tests/data/test_tree_empty_memory_range.dts) -o $(out)",
out: ["data/test_tree_empty_memory_range.dtb"],
}
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 76de93b..c1caa56 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -394,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 {
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/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/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 600c912..c63ed4c 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -483,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)
@@ -494,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.
@@ -530,6 +539,7 @@
detect_hangup: is_app_config,
gdb_port,
vfio_devices,
+ dtbo,
dtbo_vendor,
};
let instance = Arc::new(
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_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.