Merge "Update signature of AIDL Interface::dump()" into main
diff --git a/TEST_MAPPING b/TEST_MAPPING
index 77ccc1d..a9193d7 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -105,6 +105,9 @@
"path": "packages/modules/Virtualization/rialto"
},
{
+ "path": "packages/modules/Virtualization/service_vm/client_vm_csr"
+ },
+ {
"path": "packages/modules/Virtualization/service_vm/comm"
},
{
diff --git a/apex/sign_virt_apex.py b/apex/sign_virt_apex.py
index 8257aae..029ac76 100644
--- a/apex/sign_virt_apex.py
+++ b/apex/sign_virt_apex.py
@@ -27,6 +27,7 @@
- lpmake, lpunpack, simg2img, img2simg, initrd_bootconfig
"""
import argparse
+import builtins
import hashlib
import os
import re
@@ -282,7 +283,7 @@
avb_version_bc = re.search(
r"androidboot.vbmeta.avb_version = \"([^\"]*)\"", bootconfigs).group(1)
if avb_version_curr != avb_version_bc:
- raise Exception(f'AVB version mismatch between current & one & \
+ raise builtins.Exception(f'AVB version mismatch between current & one & \
used to build bootconfigs:{avb_version_curr}&{avb_version_bc}')
def calc_vbmeta_digest():
@@ -430,21 +431,32 @@
# unpacked files (will be unpacked from super.img below)
system_a_img = os.path.join(unpack_dir.name, 'system_a.img')
+ vendor_a_img = os.path.join(unpack_dir.name, 'vendor_a.img')
# re-sign super.img
# 1. unpack super.img
- # 2. resign system
- # 3. repack super.img out of resigned system
+ # 2. resign system and vendor (if exists)
+ # 3. repack super.img out of resigned system and vendor (if exists)
UnpackSuperImg(args, files['super.img'], unpack_dir.name)
system_a_f = Async(AddHashTreeFooter, args, key, system_a_img)
partitions = {"system_a": system_a_img}
+ images = [system_a_img]
+ images_f = [system_a_f]
+
+ # if vendor_a.img exists, resign it
+ if os.path.exists(vendor_a_img):
+ partitions.update({'vendor_a': vendor_a_img})
+ images.append(vendor_a_img)
+ vendor_a_f = Async(AddHashTreeFooter, args, key, vendor_a_img)
+ images_f.append(vendor_a_f)
+
Async(MakeSuperImage, args, partitions,
- files['super.img'], wait=[system_a_f])
+ files['super.img'], wait=images_f)
# re-generate vbmeta from re-signed system_a.img
vbmeta_f = Async(MakeVbmetaImage, args, key, files['vbmeta.img'],
- images=[system_a_img],
- wait=[system_a_f])
+ images=images,
+ wait=images_f)
vbmeta_bc_f = None
if not args.do_not_update_bootconfigs:
diff --git a/libs/bssl/error/src/lib.rs b/libs/bssl/error/src/lib.rs
index 3766c41..88929af 100644
--- a/libs/bssl/error/src/lib.rs
+++ b/libs/bssl/error/src/lib.rs
@@ -56,11 +56,13 @@
BN_bn2bin_padded,
CBB_flush,
CBB_len,
+ EC_GROUP_new_by_curve_name,
EC_KEY_check_key,
EC_KEY_generate_key,
EC_KEY_get0_group,
EC_KEY_get0_public_key,
EC_KEY_marshal_private_key,
+ EC_KEY_parse_private_key,
EC_KEY_new_by_curve_name,
EC_POINT_get_affine_coordinates,
EVP_AEAD_CTX_new,
diff --git a/libs/bssl/src/cbs.rs b/libs/bssl/src/cbs.rs
new file mode 100644
index 0000000..9718903
--- /dev/null
+++ b/libs/bssl/src/cbs.rs
@@ -0,0 +1,55 @@
+// 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.
+
+//! Helpers for using BoringSSL CBS (crypto byte string) objects.
+
+use bssl_ffi::{CBS_init, CBS};
+use core::marker::PhantomData;
+use core::mem::MaybeUninit;
+
+/// CRYPTO ByteString.
+///
+/// Wraps a `CBS` that references an existing fixed-sized buffer; no memory is allocated, but the
+/// buffer cannot grow.
+pub struct Cbs<'a> {
+ cbs: CBS,
+ /// The CBS contains a mutable reference to the buffer, disguised as a pointer.
+ /// Make sure the borrow checker knows that.
+ _buffer: PhantomData<&'a [u8]>,
+}
+
+impl<'a> Cbs<'a> {
+ /// Creates a new CBS that points to the given buffer.
+ pub fn new(buffer: &'a [u8]) -> Self {
+ let mut cbs = MaybeUninit::uninit();
+ // SAFETY: `CBS_init()` only sets `cbs` to point to `buffer`. It doesn't take ownership
+ // of data.
+ unsafe { CBS_init(cbs.as_mut_ptr(), buffer.as_ptr(), buffer.len()) };
+ // SAFETY: `cbs` has just been initialized by `CBS_init()`.
+ let cbs = unsafe { cbs.assume_init() };
+ Self { cbs, _buffer: PhantomData }
+ }
+}
+
+impl<'a> AsRef<CBS> for Cbs<'a> {
+ fn as_ref(&self) -> &CBS {
+ &self.cbs
+ }
+}
+
+impl<'a> AsMut<CBS> for Cbs<'a> {
+ fn as_mut(&mut self) -> &mut CBS {
+ &mut self.cbs
+ }
+}
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 7038e21..4c1ba5c 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -16,14 +16,15 @@
//! BoringSSL.
use crate::cbb::CbbFixed;
+use crate::cbs::Cbs;
use crate::util::{check_int_result, to_call_failed_error};
use alloc::vec::Vec;
use bssl_avf_error::{ApiName, Error, Result};
use bssl_ffi::{
- BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_KEY_free, EC_KEY_generate_key,
- EC_KEY_get0_group, EC_KEY_get0_public_key, EC_KEY_marshal_private_key,
- EC_KEY_new_by_curve_name, EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM,
- EC_GROUP, EC_KEY, EC_POINT,
+ BN_bn2bin_padded, BN_clear_free, BN_new, CBB_flush, CBB_len, EC_GROUP_new_by_curve_name,
+ EC_KEY_check_key, EC_KEY_free, EC_KEY_generate_key, EC_KEY_get0_group, EC_KEY_get0_public_key,
+ EC_KEY_marshal_private_key, EC_KEY_new_by_curve_name, EC_KEY_parse_private_key,
+ EC_POINT_get_affine_coordinates, NID_X9_62_prime256v1, BIGNUM, EC_GROUP, EC_KEY, EC_POINT,
};
use core::ptr::{self, NonNull};
use core::result;
@@ -59,6 +60,16 @@
Ok(ec_key)
}
+ /// Performs several checks on the key. See BoringSSL doc for more details:
+ ///
+ /// https://commondatastorage.googleapis.com/chromium-boringssl-docs/ec_key.h.html#EC_KEY_check_key
+ pub fn check_key(&self) -> Result<()> {
+ // SAFETY: This function only reads the `EC_KEY` pointer, the non-null check is performed
+ // within the function.
+ let ret = unsafe { EC_KEY_check_key(self.0.as_ptr()) };
+ check_int_result(ret, ApiName::EC_KEY_check_key)
+ }
+
/// Generates a random, private key, calculates the corresponding public key and stores both
/// in the `EC_KEY`.
fn generate_key(&mut self) -> Result<()> {
@@ -124,10 +135,34 @@
}
}
+ /// Constructs an `EcKey` instance from the provided DER-encoded ECPrivateKey slice.
+ ///
+ /// Currently, only the EC P-256 curve is supported.
+ pub fn from_ec_private_key(der_encoded_ec_private_key: &[u8]) -> Result<Self> {
+ // SAFETY: This function only returns a pointer to a static object, and the
+ // return is checked below.
+ let ec_group = unsafe {
+ EC_GROUP_new_by_curve_name(NID_X9_62_prime256v1) // EC P-256 CURVE Nid
+ };
+ if ec_group.is_null() {
+ return Err(to_call_failed_error(ApiName::EC_GROUP_new_by_curve_name));
+ }
+ let mut cbs = Cbs::new(der_encoded_ec_private_key);
+ // SAFETY: The function only reads bytes from the buffer managed by the valid `CBS`
+ // object, and the returned EC_KEY is checked.
+ let ec_key = unsafe { EC_KEY_parse_private_key(cbs.as_mut(), ec_group) };
+
+ let ec_key = NonNull::new(ec_key)
+ .map(Self)
+ .ok_or(to_call_failed_error(ApiName::EC_KEY_parse_private_key))?;
+ ec_key.check_key()?;
+ Ok(ec_key)
+ }
+
/// Returns the DER-encoded ECPrivateKey structure described in RFC 5915 Section 3:
///
/// https://datatracker.ietf.org/doc/html/rfc5915#section-3
- pub fn private_key(&self) -> Result<ZVec> {
+ pub fn ec_private_key(&self) -> Result<ZVec> {
const CAPACITY: usize = 256;
let mut buf = Zeroizing::new([0u8; CAPACITY]);
let mut cbb = CbbFixed::new(buf.as_mut());
diff --git a/libs/bssl/src/lib.rs b/libs/bssl/src/lib.rs
index 709e8ad..de81368 100644
--- a/libs/bssl/src/lib.rs
+++ b/libs/bssl/src/lib.rs
@@ -20,6 +20,7 @@
mod aead;
mod cbb;
+mod cbs;
mod digest;
mod ec_key;
mod err;
@@ -32,6 +33,7 @@
pub use aead::{Aead, AeadContext, AES_GCM_NONCE_LENGTH};
pub use cbb::CbbFixed;
+pub use cbs::Cbs;
pub use digest::Digester;
pub use ec_key::{EcKey, ZVec};
pub use hkdf::hkdf;
diff --git a/libs/bssl/tests/eckey_test.rs b/libs/bssl/tests/eckey_test.rs
new file mode 100644
index 0000000..a013fba
--- /dev/null
+++ b/libs/bssl/tests/eckey_test.rs
@@ -0,0 +1,25 @@
+// 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.
+
+use bssl_avf::{EcKey, Result};
+
+#[test]
+fn ec_private_key_serialization() -> Result<()> {
+ let ec_key = EcKey::new_p256()?;
+ let der_encoded_ec_private_key = ec_key.ec_private_key()?;
+ let deserialized_ec_key = EcKey::from_ec_private_key(der_encoded_ec_private_key.as_slice())?;
+
+ assert_eq!(ec_key.cose_public_key()?, deserialized_ec_key.cose_public_key()?);
+ Ok(())
+}
diff --git a/libs/bssl/tests/tests.rs b/libs/bssl/tests/tests.rs
index 4c0b0b0..02666d8 100644
--- a/libs/bssl/tests/tests.rs
+++ b/libs/bssl/tests/tests.rs
@@ -15,5 +15,6 @@
//! API tests of the crate `bssl_avf`.
mod aead_test;
+mod eckey_test;
mod hkdf_test;
mod hmac_test;
diff --git a/libs/vbmeta/Android.bp b/libs/vbmeta/Android.bp
index ae83703..4fb6ae4 100644
--- a/libs/vbmeta/Android.bp
+++ b/libs/vbmeta/Android.bp
@@ -18,6 +18,9 @@
rust_library {
name: "libvbmeta_rust",
defaults: ["libvbmeta_rust.defaults"],
+ apex_available: [
+ "com.android.virt",
+ ],
}
rust_test_host {
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 42ff4b0..4e735e6 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -243,7 +243,35 @@
"echo ro.product.cpu.abi=arm64-v8a) > $(out)",
}
-logical_partition {
+// Need to keep microdroid_vendor for the release configurations that don't
+// have RELEASE_AVF_ENABLE_VENDOR_MODULES build flag enabled.
+android_filesystem {
+ name: "microdroid_vendor",
+ partition_name: "vendor",
+ use_avb: true,
+ avb_private_key: ":microdroid_sign_key",
+ avb_algorithm: "SHA256_RSA4096",
+ avb_hash_algorithm: "sha256",
+ file_contexts: ":microdroid_vendor_file_contexts.gen",
+ // For deterministic output, use fake_timestamp, hard-coded uuid
+ fake_timestamp: "1611569676",
+ // python -c "import uuid; print(uuid.uuid5(uuid.NAMESPACE_URL, 'www.android.com/avf/microdroid/vendor'))"
+ uuid: "156d40d7-8d8e-5c99-8913-ec82de549a70",
+}
+
+soong_config_module_type {
+ name: "flag_aware_microdroid_super_partition",
+ module_type: "logical_partition",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_avf_enable_vendor_modules",
+ ],
+ properties: [
+ "default_group",
+ ],
+}
+
+flag_aware_microdroid_super_partition {
name: "microdroid_super",
sparse: true,
size: "auto",
@@ -253,6 +281,16 @@
filesystem: ":microdroid",
},
],
+ soong_config_variables: {
+ release_avf_enable_vendor_modules: {
+ default_group: [
+ {
+ name: "vendor_a",
+ filesystem: ":microdroid_vendor",
+ },
+ ],
+ },
+ },
}
android_filesystem {
@@ -330,13 +368,30 @@
srcs: [":avb_testkey_rsa4096"],
}
-vbmeta {
+soong_config_module_type {
+ name: "flag_aware_microdroid_vbmeta",
+ module_type: "vbmeta",
+ config_namespace: "ANDROID",
+ bool_variables: [
+ "release_avf_enable_vendor_modules",
+ ],
+ properties: [
+ "partitions",
+ ],
+}
+
+flag_aware_microdroid_vbmeta {
name: "microdroid_vbmeta",
partition_name: "vbmeta",
private_key: ":microdroid_sign_key",
partitions: [
"microdroid",
],
+ soong_config_variables: {
+ release_avf_enable_vendor_modules: {
+ partitions: ["microdroid_vendor"],
+ },
+ },
}
prebuilt_etc {
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 93f49ef..8481edf 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -23,6 +23,7 @@
"libbinder_rs",
"libbyteorder",
"libcap_rust",
+ "libclient_vm_csr",
"libciborium",
"libcoset",
"libdiced_open_dice",
@@ -46,12 +47,10 @@
"libserde",
"libserde_cbor",
"libserde_json",
- "libservice_vm_comm",
"libthiserror",
"libuuid",
"libvsock",
"librand",
- "libzeroize",
],
init_rc: ["microdroid_manager.rc"],
multilib: {
@@ -72,7 +71,6 @@
defaults: ["microdroid_manager_defaults"],
test_suites: ["general-tests"],
rustlibs: [
- "libhwtrust",
"libtempfile",
],
multilib: {
diff --git a/microdroid_manager/src/vm_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index 0661314..d3346d8 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -22,31 +22,12 @@
use anyhow::{anyhow, Context, Result};
use avflog::LogResult;
use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult, Status};
-use diced_open_dice::{DiceArtifacts, derive_cdi_leaf_priv, PrivateKey, sign};
+use client_vm_csr::{generate_attestation_key_and_csr, ClientVmAttestationData};
+use diced_open_dice::DiceArtifacts;
use log::info;
use rpcbinder::RpcServer;
-
use crate::vm_secret::VmSecret;
-use coset::{
- iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
- CoseSignatureBuilder, HeaderBuilder,
-};
-use openssl::{
- bn::{BigNum, BigNumContext},
- ec::{EcGroup, EcKey, EcKeyRef},
- ecdsa::EcdsaSig,
- nid::Nid,
- pkey::Private,
- sha::sha256,
-};
-use service_vm_comm::{Csr, CsrPayload};
use std::os::unix::io::OwnedFd;
-use zeroize::Zeroizing;
-
-const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
-const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
-const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
-const ATTESTATION_KEY_AFFINE_COORDINATE_SIZE: i32 = 32;
/// Implementation of `IVmPayloadService`.
struct VmPayloadService {
@@ -90,11 +71,21 @@
fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
self.check_restricted_apis_allowed()?;
- let (private_key, csr) = generate_attestation_key_and_csr(challenge, self.secret.dice())
+ let ClientVmAttestationData { private_key, csr } =
+ generate_attestation_key_and_csr(challenge, self.secret.dice())
+ .map_err(|e| {
+ Status::new_service_specific_error_str(
+ STATUS_FAILED_TO_PREPARE_CSR_AND_KEY,
+ Some(format!("Failed to prepare the CSR and key pair: {e:?}")),
+ )
+ })
+ .with_log()?;
+ let csr = csr
+ .into_cbor_vec()
.map_err(|e| {
Status::new_service_specific_error_str(
STATUS_FAILED_TO_PREPARE_CSR_AND_KEY,
- Some(format!("Failed to prepare the CSR and key pair: {e:?}")),
+ Some(format!("Failed to serialize CSR into CBOR: {e:?}")),
)
})
.with_log()?;
@@ -106,93 +97,6 @@
}
}
-fn generate_attestation_key_and_csr(
- challenge: &[u8],
- dice_artifacts: &dyn DiceArtifacts,
-) -> Result<(Zeroizing<Vec<u8>>, Vec<u8>)> {
- let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
- let attestation_key = EcKey::generate(&group)?;
- let csr = build_csr(challenge, attestation_key.as_ref(), dice_artifacts)?;
-
- let csr = csr.into_cbor_vec().context("Failed to serialize CSR")?;
- let private_key = attestation_key.private_key_to_der()?;
- Ok((Zeroizing::new(private_key), csr))
-}
-
-fn build_csr(
- challenge: &[u8],
- attestation_key: &EcKeyRef<Private>,
- dice_artifacts: &dyn DiceArtifacts,
-) -> Result<Csr> {
- // Builds CSR Payload to be signed.
- let public_key =
- to_cose_public_key(attestation_key)?.to_vec().context("Failed to serialize public key")?;
- let csr_payload = CsrPayload { public_key, challenge: challenge.to_vec() };
- let csr_payload = csr_payload.into_cbor_vec()?;
-
- // Builds signed CSR Payload.
- let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts)?;
- let signed_csr_payload = build_signed_data(csr_payload, &cdi_leaf_priv, attestation_key)?
- .to_vec()
- .context("Failed to serialize signed CSR payload")?;
-
- // Builds CSR.
- let dice_cert_chain = dice_artifacts.bcc().ok_or(anyhow!("bcc is none"))?.to_vec();
- Ok(Csr { dice_cert_chain, signed_csr_payload })
-}
-
-fn build_signed_data(
- payload: Vec<u8>,
- cdi_leaf_priv: &PrivateKey,
- attestation_key: &EcKeyRef<Private>,
-) -> Result<CoseSign> {
- let cdi_leaf_sig_headers = build_signature_headers(iana::Algorithm::EdDSA);
- let attestation_key_sig_headers = build_signature_headers(ATTESTATION_KEY_ALGO);
- let aad = &[];
- let signed_data = CoseSignBuilder::new()
- .payload(payload)
- .try_add_created_signature(cdi_leaf_sig_headers, aad, |message| {
- sign(message, cdi_leaf_priv.as_array()).map(|v| v.to_vec())
- })?
- .try_add_created_signature(attestation_key_sig_headers, aad, |message| {
- ecdsa_sign(message, attestation_key)
- })?
- .build();
- Ok(signed_data)
-}
-
-/// Builds a signature with headers filled with the provided algorithm.
-/// The signature data will be filled later when building the signed data.
-fn build_signature_headers(alg: iana::Algorithm) -> CoseSignature {
- let protected = HeaderBuilder::new().algorithm(alg).build();
- CoseSignatureBuilder::new().protected(protected).build()
-}
-
-fn ecdsa_sign(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>> {
- let digest = sha256(message);
- // Passes the digest to `ECDSA_do_sign` as recommended in the spec:
- // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ecdsa.h.html#ECDSA_do_sign
- let sig = EcdsaSig::sign::<Private>(&digest, key)?;
- Ok(sig.to_der()?)
-}
-
-fn get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)> {
- let mut ctx = BigNumContext::new()?;
- let mut x = BigNum::new()?;
- let mut y = BigNum::new()?;
- key.public_key().affine_coordinates_gfp(key.group(), &mut x, &mut y, &mut ctx)?;
- let x = x.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
- let y = y.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
- Ok((x, y))
-}
-
-fn to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey> {
- let (x, y) = get_affine_coordinates(key)?;
- Ok(CoseKeyBuilder::new_ec2_pub_key(ATTESTATION_KEY_CURVE, x, y)
- .algorithm(ATTESTATION_KEY_ALGO)
- .build())
-}
-
impl Interface for VmPayloadService {}
impl VmPayloadService {
@@ -237,106 +141,3 @@
});
Ok(())
}
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use anyhow::bail;
- use ciborium::Value;
- use coset::{iana::EnumI64, Label};
- use hwtrust::{dice, session::Session};
- use openssl::pkey::Public;
-
- /// The following data is generated randomly with urandom.
- const CHALLENGE: [u8; 16] = [
- 0xb3, 0x66, 0xfa, 0x72, 0x92, 0x32, 0x2c, 0xd4, 0x99, 0xcb, 0x00, 0x1f, 0x0e, 0xe0, 0xc7,
- 0x41,
- ];
-
- #[test]
- fn csr_and_private_key_have_correct_format() -> Result<()> {
- let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
-
- let (private_key, csr) = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
- let ec_private_key = EcKey::private_key_from_der(&private_key)?;
- let csr = Csr::from_cbor_slice(&csr).unwrap();
- let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload).unwrap();
- let aad = &[];
-
- // Checks CSR payload.
- let csr_payload =
- cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
- let public_key = to_cose_public_key(&ec_private_key)?.to_vec().unwrap();
- let expected_csr_payload = CsrPayload { challenge: CHALLENGE.to_vec(), public_key };
- assert_eq!(expected_csr_payload, csr_payload);
-
- // Checks the first signature is signed with CDI_Leaf_Priv.
- let session = Session::default();
- let chain = dice::Chain::from_cbor(&session, &csr.dice_cert_chain)?;
- let public_key = chain.leaf().subject_public_key();
- cose_sign
- .verify_signature(0, aad, |signature, message| public_key.verify(signature, message))?;
-
- // Checks the second signature is signed with attestation key.
- let attestation_public_key = CoseKey::from_slice(&csr_payload.public_key).unwrap();
- let ec_public_key = to_ec_public_key(&attestation_public_key)?;
- cose_sign.verify_signature(1, aad, |signature, message| {
- ecdsa_verify(signature, message, &ec_public_key)
- })?;
-
- // Verifies that private key and the public key form a valid key pair.
- let message = b"test message";
- let signature = ecdsa_sign(message, &ec_private_key)?;
- ecdsa_verify(&signature, message, &ec_public_key)?;
-
- Ok(())
- }
-
- fn ecdsa_verify(
- signature: &[u8],
- message: &[u8],
- ec_public_key: &EcKeyRef<Public>,
- ) -> Result<()> {
- let sig = EcdsaSig::from_der(signature)?;
- let digest = sha256(message);
- if sig.verify(&digest, ec_public_key)? {
- Ok(())
- } else {
- bail!("Signature does not match")
- }
- }
-
- fn to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>> {
- check_ec_key_params(cose_key)?;
- let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
- let x = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
- let y = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
- let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
- key.check_key()?;
- Ok(key)
- }
-
- fn check_ec_key_params(cose_key: &CoseKey) -> Result<()> {
- assert_eq!(coset::KeyType::Assigned(iana::KeyType::EC2), cose_key.kty);
- assert_eq!(Some(coset::Algorithm::Assigned(ATTESTATION_KEY_ALGO)), cose_key.alg);
- let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
- assert_eq!(&Value::from(ATTESTATION_KEY_CURVE.to_i64()), crv);
- Ok(())
- }
-
- fn get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum> {
- get_label_value(key, label)?
- .as_bytes()
- .map(|v| BigNum::from_slice(&v[..]).unwrap())
- .ok_or_else(|| anyhow!("Value not a bstr."))
- }
-
- fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
- Ok(&key
- .params
- .iter()
- .find(|(k, _)| k == &label)
- .ok_or_else(|| anyhow!("Label {:?} not found", label))?
- .1)
- }
-}
diff --git a/rialto/Android.bp b/rialto/Android.bp
index 326f6fc..28c261e 100644
--- a/rialto/Android.bp
+++ b/rialto/Android.bp
@@ -109,6 +109,8 @@
"libandroid_logger",
"libanyhow",
"libciborium",
+ "libclient_vm_csr",
+ "libdiced_sample_inputs",
"liblibc",
"liblog_rust",
"libservice_vm_comm",
diff --git a/rialto/tests/test.rs b/rialto/tests/test.rs
index e13b7a1..0f59350 100644
--- a/rialto/tests/test.rs
+++ b/rialto/tests/test.rs
@@ -23,9 +23,11 @@
};
use anyhow::{bail, Context, Result};
use ciborium::value::Value;
+use client_vm_csr::generate_attestation_key_and_csr;
use log::info;
use service_vm_comm::{
- EcdsaP256KeyPair, GenerateCertificateRequestParams, Request, Response, VmType,
+ ClientVmAttestationParams, EcdsaP256KeyPair, GenerateCertificateRequestParams, Request,
+ RequestProcessingError, Response, VmType,
};
use service_vm_manager::ServiceVm;
use std::fs::File;
@@ -51,8 +53,9 @@
let mut vm = start_service_vm(vm_type)?;
check_processing_reverse_request(&mut vm)?;
- let maced_public_key = check_processing_generating_key_pair_request(&mut vm)?;
- check_processing_generating_certificate_request(&mut vm, maced_public_key)?;
+ 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)?;
Ok(())
}
@@ -68,17 +71,17 @@
Ok(())
}
-fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<Vec<u8>> {
+fn check_processing_generating_key_pair_request(vm: &mut ServiceVm) -> Result<EcdsaP256KeyPair> {
let request = Request::GenerateEcdsaP256KeyPair;
let response = vm.process_request(request)?;
info!("Received response: {response:?}.");
match response {
- Response::GenerateEcdsaP256KeyPair(EcdsaP256KeyPair { maced_public_key, key_blob }) => {
- assert_array_has_nonzero(&maced_public_key);
- assert_array_has_nonzero(&key_blob);
- Ok(maced_public_key)
+ Response::GenerateEcdsaP256KeyPair(key_pair) => {
+ assert_array_has_nonzero(&key_pair.maced_public_key);
+ assert_array_has_nonzero(&key_pair.key_blob);
+ Ok(key_pair)
}
_ => bail!("Incorrect response type: {response:?}"),
}
@@ -90,10 +93,10 @@
fn check_processing_generating_certificate_request(
vm: &mut ServiceVm,
- maced_public_key: Vec<u8>,
+ maced_public_key: &[u8],
) -> Result<()> {
let params = GenerateCertificateRequestParams {
- keys_to_sign: vec![maced_public_key],
+ keys_to_sign: vec![maced_public_key.to_vec()],
challenge: vec![],
};
let request = Request::GenerateCertificateRequest(params);
@@ -107,6 +110,31 @@
}
}
+fn check_attestation_request(vm: &mut ServiceVm, key_blob: &[u8]) -> 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,
+ 0x5c,
+ ];
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+ let attestation_data = generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+
+ let params = ClientVmAttestationParams {
+ csr: attestation_data.csr.into_cbor_vec()?,
+ remotely_provisioned_key_blob: key_blob.to_vec(),
+ };
+ let request = Request::RequestClientVmAttestation(params);
+
+ let response = vm.process_request(request)?;
+ info!("Received response: {response:?}.");
+
+ match response {
+ // TODO(b/309441500): Check the certificate once it is implemented.
+ Response::Err(RequestProcessingError::OperationUnimplemented) => Ok(()),
+ _ => bail!("Incorrect response type: {response:?}"),
+ }
+}
+
/// 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/service_vm/client_vm_csr/Android.bp b/service_vm/client_vm_csr/Android.bp
new file mode 100644
index 0000000..8d738d8
--- /dev/null
+++ b/service_vm/client_vm_csr/Android.bp
@@ -0,0 +1,37 @@
+package {
+ default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_defaults {
+ name: "libclient_vm_csr_defaults",
+ crate_name: "client_vm_csr",
+ srcs: ["src/lib.rs"],
+ rustlibs: [
+ "libanyhow",
+ "libcoset",
+ "libdiced_open_dice",
+ "libopenssl",
+ "libservice_vm_comm",
+ "libzeroize",
+ ],
+}
+
+rust_library {
+ name: "libclient_vm_csr",
+ defaults: ["libclient_vm_csr_defaults"],
+ prefer_rlib: true,
+ apex_available: [
+ "com.android.virt",
+ ],
+}
+
+rust_test {
+ name: "libclient_vm_csr.test",
+ defaults: ["libclient_vm_csr_defaults"],
+ test_suites: ["general-tests"],
+ rustlibs: [
+ "libciborium",
+ "libdiced_sample_inputs",
+ "libhwtrust",
+ ],
+}
diff --git a/service_vm/client_vm_csr/TEST_MAPPING b/service_vm/client_vm_csr/TEST_MAPPING
new file mode 100644
index 0000000..5bc06c0
--- /dev/null
+++ b/service_vm/client_vm_csr/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" : "libclient_vm_csr.test"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/service_vm/client_vm_csr/src/lib.rs b/service_vm/client_vm_csr/src/lib.rs
new file mode 100644
index 0000000..512ecaf
--- /dev/null
+++ b/service_vm/client_vm_csr/src/lib.rs
@@ -0,0 +1,242 @@
+// 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.
+
+//! Generate the attestation key and CSR for client VM in the remote
+//! attestation.
+
+use anyhow::{anyhow, Context, Result};
+use coset::{
+ iana, CborSerializable, CoseKey, CoseKeyBuilder, CoseSign, CoseSignBuilder, CoseSignature,
+ CoseSignatureBuilder, HeaderBuilder,
+};
+use diced_open_dice::{derive_cdi_leaf_priv, sign, DiceArtifacts, PrivateKey};
+use openssl::{
+ bn::{BigNum, BigNumContext},
+ ec::{EcGroup, EcKey, EcKeyRef},
+ ecdsa::EcdsaSig,
+ nid::Nid,
+ pkey::Private,
+ sha::sha256,
+};
+use service_vm_comm::{Csr, CsrPayload};
+use zeroize::Zeroizing;
+
+/// Key parameters for the attestation key.
+///
+/// See service_vm/comm/client_vm_csr.cddl for more information about the attestation key.
+const ATTESTATION_KEY_NID: Nid = Nid::X9_62_PRIME256V1; // NIST P-256 curve
+const ATTESTATION_KEY_ALGO: iana::Algorithm = iana::Algorithm::ES256;
+const ATTESTATION_KEY_CURVE: iana::EllipticCurve = iana::EllipticCurve::P_256;
+const ATTESTATION_KEY_AFFINE_COORDINATE_SIZE: i32 = 32;
+
+/// Represents the output of generating the attestation key and CSR for the client VM.
+pub struct ClientVmAttestationData {
+ /// DER-encoded ECPrivateKey to be attested.
+ pub private_key: Zeroizing<Vec<u8>>,
+
+ /// CSR containing client VM information and the public key corresponding to the
+ /// private key to be attested.
+ pub csr: Csr,
+}
+
+/// Generates the attestation key and CSR including the public key to be attested for the
+/// client VM in remote attestation.
+pub fn generate_attestation_key_and_csr(
+ challenge: &[u8],
+ dice_artifacts: &dyn DiceArtifacts,
+) -> Result<ClientVmAttestationData> {
+ let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+ let attestation_key = EcKey::generate(&group)?;
+
+ let csr = build_csr(challenge, attestation_key.as_ref(), dice_artifacts)?;
+ let private_key = attestation_key.private_key_to_der()?;
+ Ok(ClientVmAttestationData { private_key: Zeroizing::new(private_key), csr })
+}
+
+fn build_csr(
+ challenge: &[u8],
+ attestation_key: &EcKeyRef<Private>,
+ dice_artifacts: &dyn DiceArtifacts,
+) -> Result<Csr> {
+ // Builds CSR Payload to be signed.
+ let public_key =
+ to_cose_public_key(attestation_key)?.to_vec().context("Failed to serialize public key")?;
+ let csr_payload = CsrPayload { public_key, challenge: challenge.to_vec() };
+ let csr_payload = csr_payload.into_cbor_vec()?;
+
+ // Builds signed CSR Payload.
+ let cdi_leaf_priv = derive_cdi_leaf_priv(dice_artifacts)?;
+ let signed_csr_payload = build_signed_data(csr_payload, &cdi_leaf_priv, attestation_key)?
+ .to_vec()
+ .context("Failed to serialize signed CSR payload")?;
+
+ // Builds CSR.
+ let dice_cert_chain = dice_artifacts.bcc().ok_or(anyhow!("bcc is none"))?.to_vec();
+ Ok(Csr { dice_cert_chain, signed_csr_payload })
+}
+
+fn build_signed_data(
+ payload: Vec<u8>,
+ cdi_leaf_priv: &PrivateKey,
+ attestation_key: &EcKeyRef<Private>,
+) -> Result<CoseSign> {
+ let cdi_leaf_sig_headers = build_signature_headers(iana::Algorithm::EdDSA);
+ let attestation_key_sig_headers = build_signature_headers(ATTESTATION_KEY_ALGO);
+ let aad = &[];
+ let signed_data = CoseSignBuilder::new()
+ .payload(payload)
+ .try_add_created_signature(cdi_leaf_sig_headers, aad, |message| {
+ sign(message, cdi_leaf_priv.as_array()).map(|v| v.to_vec())
+ })?
+ .try_add_created_signature(attestation_key_sig_headers, aad, |message| {
+ ecdsa_sign(message, attestation_key)
+ })?
+ .build();
+ Ok(signed_data)
+}
+
+/// Builds a signature with headers filled with the provided algorithm.
+/// The signature data will be filled later when building the signed data.
+fn build_signature_headers(alg: iana::Algorithm) -> CoseSignature {
+ let protected = HeaderBuilder::new().algorithm(alg).build();
+ CoseSignatureBuilder::new().protected(protected).build()
+}
+
+fn ecdsa_sign(message: &[u8], key: &EcKeyRef<Private>) -> Result<Vec<u8>> {
+ let digest = sha256(message);
+ // Passes the digest to `ECDSA_do_sign` as recommended in the spec:
+ // https://commondatastorage.googleapis.com/chromium-boringssl-docs/ecdsa.h.html#ECDSA_do_sign
+ let sig = EcdsaSig::sign::<Private>(&digest, key)?;
+ Ok(sig.to_der()?)
+}
+
+fn get_affine_coordinates(key: &EcKeyRef<Private>) -> Result<(Vec<u8>, Vec<u8>)> {
+ let mut ctx = BigNumContext::new()?;
+ let mut x = BigNum::new()?;
+ let mut y = BigNum::new()?;
+ key.public_key().affine_coordinates_gfp(key.group(), &mut x, &mut y, &mut ctx)?;
+ let x = x.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+ let y = y.to_vec_padded(ATTESTATION_KEY_AFFINE_COORDINATE_SIZE)?;
+ Ok((x, y))
+}
+
+fn to_cose_public_key(key: &EcKeyRef<Private>) -> Result<CoseKey> {
+ let (x, y) = get_affine_coordinates(key)?;
+ Ok(CoseKeyBuilder::new_ec2_pub_key(ATTESTATION_KEY_CURVE, x, y)
+ .algorithm(ATTESTATION_KEY_ALGO)
+ .build())
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use anyhow::bail;
+ use ciborium::Value;
+ use coset::{iana::EnumI64, Label};
+ use hwtrust::{dice, session::Session};
+ use openssl::pkey::Public;
+
+ /// The following data was generated randomly with urandom.
+ const CHALLENGE: [u8; 16] = [
+ 0xb3, 0x66, 0xfa, 0x72, 0x92, 0x32, 0x2c, 0xd4, 0x99, 0xcb, 0x00, 0x1f, 0x0e, 0xe0, 0xc7,
+ 0x41,
+ ];
+
+ #[test]
+ fn csr_and_private_key_have_correct_format() -> Result<()> {
+ let dice_artifacts = diced_sample_inputs::make_sample_bcc_and_cdis()?;
+
+ let ClientVmAttestationData { private_key, csr } =
+ generate_attestation_key_and_csr(&CHALLENGE, &dice_artifacts)?;
+ let ec_private_key = EcKey::private_key_from_der(&private_key)?;
+ let cose_sign = CoseSign::from_slice(&csr.signed_csr_payload).unwrap();
+ let aad = &[];
+
+ // Checks CSR payload.
+ let csr_payload =
+ cose_sign.payload.as_ref().and_then(|v| CsrPayload::from_cbor_slice(v).ok()).unwrap();
+ let public_key = to_cose_public_key(&ec_private_key)?.to_vec().unwrap();
+ let expected_csr_payload = CsrPayload { challenge: CHALLENGE.to_vec(), public_key };
+ assert_eq!(expected_csr_payload, csr_payload);
+
+ // Checks the first signature is signed with CDI_Leaf_Priv.
+ let session = Session::default();
+ let chain = dice::Chain::from_cbor(&session, &csr.dice_cert_chain)?;
+ let public_key = chain.leaf().subject_public_key();
+ cose_sign
+ .verify_signature(0, aad, |signature, message| public_key.verify(signature, message))?;
+
+ // Checks the second signature is signed with attestation key.
+ let attestation_public_key = CoseKey::from_slice(&csr_payload.public_key).unwrap();
+ let ec_public_key = to_ec_public_key(&attestation_public_key)?;
+ cose_sign.verify_signature(1, aad, |signature, message| {
+ ecdsa_verify(signature, message, &ec_public_key)
+ })?;
+
+ // Verifies that private key and the public key form a valid key pair.
+ let message = b"test message";
+ let signature = ecdsa_sign(message, &ec_private_key)?;
+ ecdsa_verify(&signature, message, &ec_public_key)?;
+
+ Ok(())
+ }
+
+ fn ecdsa_verify(
+ signature: &[u8],
+ message: &[u8],
+ ec_public_key: &EcKeyRef<Public>,
+ ) -> Result<()> {
+ let sig = EcdsaSig::from_der(signature)?;
+ let digest = sha256(message);
+ if sig.verify(&digest, ec_public_key)? {
+ Ok(())
+ } else {
+ bail!("Signature does not match")
+ }
+ }
+
+ fn to_ec_public_key(cose_key: &CoseKey) -> Result<EcKey<Public>> {
+ check_ec_key_params(cose_key)?;
+ let group = EcGroup::from_curve_name(ATTESTATION_KEY_NID)?;
+ let x = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::X.to_i64()))?;
+ let y = get_label_value_as_bignum(cose_key, Label::Int(iana::Ec2KeyParameter::Y.to_i64()))?;
+ let key = EcKey::from_public_key_affine_coordinates(&group, &x, &y)?;
+ key.check_key()?;
+ Ok(key)
+ }
+
+ fn check_ec_key_params(cose_key: &CoseKey) -> Result<()> {
+ assert_eq!(coset::KeyType::Assigned(iana::KeyType::EC2), cose_key.kty);
+ assert_eq!(Some(coset::Algorithm::Assigned(ATTESTATION_KEY_ALGO)), cose_key.alg);
+ let crv = get_label_value(cose_key, Label::Int(iana::Ec2KeyParameter::Crv.to_i64()))?;
+ assert_eq!(&Value::from(ATTESTATION_KEY_CURVE.to_i64()), crv);
+ Ok(())
+ }
+
+ fn get_label_value_as_bignum(key: &CoseKey, label: Label) -> Result<BigNum> {
+ get_label_value(key, label)?
+ .as_bytes()
+ .map(|v| BigNum::from_slice(&v[..]).unwrap())
+ .ok_or_else(|| anyhow!("Value not a bstr."))
+ }
+
+ fn get_label_value(key: &CoseKey, label: Label) -> Result<&Value> {
+ Ok(&key
+ .params
+ .iter()
+ .find(|(k, _)| k == &label)
+ .ok_or_else(|| anyhow!("Label {:?} not found", label))?
+ .1)
+ }
+}
diff --git a/service_vm/requests/src/client_vm.rs b/service_vm/requests/src/client_vm.rs
index 1081f3a..74c26d3 100644
--- a/service_vm/requests/src/client_vm.rs
+++ b/service_vm/requests/src/client_vm.rs
@@ -17,10 +17,12 @@
use crate::keyblob::decrypt_private_key;
use alloc::vec::Vec;
+use bssl_avf::EcKey;
use core::result;
+use coset::{CborSerializable, CoseSign};
use diced_open_dice::DiceArtifacts;
use log::error;
-use service_vm_comm::{ClientVmAttestationParams, RequestProcessingError};
+use service_vm_comm::{ClientVmAttestationParams, Csr, RequestProcessingError};
type Result<T> = result::Result<T, RequestProcessingError>;
@@ -28,19 +30,22 @@
params: ClientVmAttestationParams,
dice_artifacts: &dyn DiceArtifacts,
) -> Result<Vec<u8>> {
- // TODO(b/309440321): Verify the signatures in the csr.
+ let csr = Csr::from_cbor_slice(¶ms.csr)?;
+ let _cose_sign = CoseSign::from_slice(&csr.signed_csr_payload)?;
+ // TODO(b/309440321): Verify the signatures in the `_cose_sign`.
- // TODO(b/278717513): Compare client VM's DICE chain up to pvmfw cert with
- // RKP VM's DICE chain.
+ // TODO(b/278717513): Compare client VM's DICE chain in the `csr` up to pvmfw
+ // cert with RKP VM's DICE chain.
- let _private_key =
+ 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.
+ // `_private_key`.
Err(RequestProcessingError::OperationUnimplemented)
}
diff --git a/service_vm/requests/src/rkp.rs b/service_vm/requests/src/rkp.rs
index 8d7d771..c2c13b3 100644
--- a/service_vm/requests/src/rkp.rs
+++ b/service_vm/requests/src/rkp.rs
@@ -48,7 +48,7 @@
let maced_public_key = build_maced_public_key(ec_key.cose_public_key()?, hmac_key.as_ref())?;
let key_blob =
- EncryptedKeyBlob::new(ec_key.private_key()?.as_slice(), dice_artifacts.cdi_seal())?;
+ EncryptedKeyBlob::new(ec_key.ec_private_key()?.as_slice(), dice_artifacts.cdi_seal())?;
let key_pair =
EcdsaP256KeyPair { maced_public_key, key_blob: cbor_util::serialize(&key_blob)? };
diff --git a/virtualizationmanager/Android.bp b/virtualizationmanager/Android.bp
index 12d8724..33897b2 100644
--- a/virtualizationmanager/Android.bp
+++ b/virtualizationmanager/Android.bp
@@ -54,6 +54,7 @@
"libshared_child",
"libstatslog_virtualization_rust",
"libtombstoned_client_rust",
+ "libvbmeta_rust",
"libvm_control",
"libvmconfig",
"libzip",
diff --git a/virtualizationmanager/src/aidl.rs b/virtualizationmanager/src/aidl.rs
index 0746c84..c6a30aa 100644
--- a/virtualizationmanager/src/aidl.rs
+++ b/virtualizationmanager/src/aidl.rs
@@ -62,6 +62,7 @@
};
use disk::QcowFile;
use lazy_static::lazy_static;
+use libfdt::Fdt;
use log::{debug, error, info, warn};
use microdroid_payload_config::{OsConfig, Task, TaskType, VmPayloadConfig};
use nix::unistd::pipe;
@@ -70,7 +71,7 @@
use semver::VersionReq;
use std::collections::HashSet;
use std::convert::TryInto;
-use std::ffi::CStr;
+use std::ffi::{CStr, CString};
use std::fs::{canonicalize, read_dir, remove_file, File, OpenOptions};
use std::io::{BufRead, BufReader, Error, ErrorKind, Write};
use std::num::{NonZeroU16, NonZeroU32};
@@ -78,6 +79,7 @@
use std::os::unix::raw::pid_t;
use std::path::{Path, PathBuf};
use std::sync::{Arc, Mutex, Weak};
+use vbmeta::VbMetaImage;
use vmconfig::VmConfig;
use vsock::VsockStream;
use zip::ZipArchive;
@@ -101,6 +103,9 @@
const UNFORMATTED_STORAGE_MAGIC: &str = "UNFORMATTED-STORAGE";
+/// Roughly estimated sufficient size for storing vendor public key into DTBO.
+const EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE: usize = 10000;
+
/// crosvm requires all partitions to be a multiple of 4KiB.
const PARTITION_GRANULARITY_BYTES: u64 = 4096;
@@ -361,6 +366,22 @@
check_gdb_allowed(config)?;
}
+ let vendor_public_key = extract_vendor_public_key(config)
+ .context("Failed to extract vendor public key")
+ .or_service_specific_exception(-1)?;
+ let dtbo_vendor = if let Some(vendor_public_key) = vendor_public_key {
+ let dtbo_for_vendor_image = temporary_directory.join("dtbo_vendor");
+ create_dtbo_for_vendor_image(&vendor_public_key, &dtbo_for_vendor_image)
+ .context("Failed to write vendor_public_key")
+ .or_service_specific_exception(-1)?;
+ let file = File::open(dtbo_for_vendor_image)
+ .context("Failed to open dtbo_vendor")
+ .or_service_specific_exception(-1)?;
+ Some(file)
+ } else {
+ None
+ };
+
let debug_level = match config {
VirtualMachineConfig::AppConfig(config) => config.debugLevel,
_ => DebugLevel::NONE,
@@ -506,6 +527,7 @@
detect_hangup: is_app_config,
gdb_port,
vfio_devices,
+ dtbo_vendor,
};
let instance = Arc::new(
VmInstance::new(
@@ -524,6 +546,68 @@
}
}
+fn extract_vendor_public_key(config: &VirtualMachineConfig) -> Result<Option<Vec<u8>>> {
+ let VirtualMachineConfig::AppConfig(config) = config else {
+ return Ok(None);
+ };
+ let Some(custom_config) = &config.customConfig else {
+ return Ok(None);
+ };
+ let Some(file) = custom_config.vendorImage.as_ref() else {
+ return Ok(None);
+ };
+
+ let file = clone_file(file)?;
+ let size = file.metadata().context("Failed to get metadata from microdroid-vendor.img")?.len();
+ let vbmeta = VbMetaImage::verify_reader_region(&file, 0, size)
+ .context("Failed to get vbmeta from microdroid-vendor.img")?;
+ let vendor_public_key = vbmeta
+ .public_key()
+ .ok_or(anyhow!("No public key is extracted from microdroid-vendor.img"))?
+ .to_vec();
+
+ Ok(Some(vendor_public_key))
+}
+
+fn create_dtbo_for_vendor_image(vendor_public_key: &[u8], dtbo: &PathBuf) -> Result<()> {
+ if dtbo.exists() {
+ return Err(anyhow!("DTBO file already exists"));
+ }
+
+ let mut buf = vec![0; EMPTY_VENDOR_DT_OVERLAY_BUF_SIZE];
+ let fdt = Fdt::create_empty_tree(buf.as_mut_slice())
+ .map_err(|e| anyhow!("Failed to create FDT: {:?}", e))?;
+ let mut root = fdt.root_mut().map_err(|e| anyhow!("Failed to get root node: {:?}", e))?;
+
+ let fragment_node_name = CString::new("fragment@0")?;
+ let mut fragment_node = root
+ .add_subnode(fragment_node_name.as_c_str())
+ .map_err(|e| anyhow!("Failed to create fragment node: {:?}", e))?;
+ let target_path_prop_name = CString::new("target-path")?;
+ let target_path = CString::new("/")?;
+ fragment_node
+ .setprop(target_path_prop_name.as_c_str(), target_path.to_bytes_with_nul())
+ .map_err(|e| anyhow!("Failed to set target-path: {:?}", e))?;
+ let overlay_node_name = CString::new("__overlay__")?;
+ let mut overlay_node = fragment_node
+ .add_subnode(overlay_node_name.as_c_str())
+ .map_err(|e| anyhow!("Failed to create overlay node: {:?}", e))?;
+
+ let avf_node_name = CString::new("avf")?;
+ let mut avf_node = overlay_node
+ .add_subnode(avf_node_name.as_c_str())
+ .map_err(|e| anyhow!("Failed to create avf node: {:?}", e))?;
+ let vendor_public_key_name = CString::new("vendor_public_key")?;
+ avf_node
+ .setprop(vendor_public_key_name.as_c_str(), vendor_public_key)
+ .map_err(|e| anyhow!("Failed to set avf/vendor_public_key: {:?}", e))?;
+
+ fdt.pack().map_err(|e| anyhow!("Failed to pack fdt: {:?}", e))?;
+ let mut file = File::create(dtbo)?;
+ file.write_all(fdt.as_slice())?;
+ Ok(file.flush()?)
+}
+
fn write_zero_filler(zero_filler_path: &Path) -> Result<()> {
let file = OpenOptions::new()
.create_new(true)
@@ -1376,4 +1460,58 @@
append_kernel_param("bar=42", &mut vm_config);
assert_eq!(vm_config.params, Some("foo=5 bar=42".to_owned()))
}
+
+ #[test]
+ fn test_create_dtbo_for_vendor_image() -> Result<()> {
+ let vendor_public_key = String::from("foo");
+ let vendor_public_key = vendor_public_key.as_bytes();
+
+ let tmp_dir = tempfile::TempDir::new()?;
+ let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
+
+ create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path)?;
+
+ let data = std::fs::read(dtbo_path)?;
+ let fdt = Fdt::from_slice(&data).unwrap();
+
+ let fragment_node_path = CString::new("/fragment@0")?;
+ let fragment_node = fdt.node(fragment_node_path.as_c_str()).unwrap();
+ let Some(fragment_node) = fragment_node else {
+ bail!("fragment_node shouldn't be None.");
+ };
+ let target_path_prop_name = CString::new("target-path")?;
+ let target_path_from_dtbo =
+ fragment_node.getprop(target_path_prop_name.as_c_str()).unwrap();
+ let target_path_expected = CString::new("/")?;
+ assert_eq!(target_path_from_dtbo, Some(target_path_expected.to_bytes_with_nul()));
+
+ let avf_node_path = CString::new("/fragment@0/__overlay__/avf")?;
+ let avf_node = fdt.node(avf_node_path.as_c_str()).unwrap();
+ let Some(avf_node) = avf_node else {
+ bail!("avf_node shouldn't be None.");
+ };
+ let vendor_public_key_name = CString::new("vendor_public_key")?;
+ let key_from_dtbo = avf_node.getprop(vendor_public_key_name.as_c_str()).unwrap();
+ assert_eq!(key_from_dtbo, Some(vendor_public_key));
+
+ tmp_dir.close()?;
+ Ok(())
+ }
+
+ #[test]
+ fn test_create_dtbo_for_vendor_image_throws_error_if_already_exists() -> Result<()> {
+ let vendor_public_key = String::from("foo");
+ let vendor_public_key = vendor_public_key.as_bytes();
+
+ let tmp_dir = tempfile::TempDir::new()?;
+ let dtbo_path = tmp_dir.path().to_path_buf().join("bar");
+
+ create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path)?;
+
+ let ret_second_trial = create_dtbo_for_vendor_image(vendor_public_key, &dtbo_path);
+ assert!(ret_second_trial.is_err(), "should fail");
+
+ tmp_dir.close()?;
+ Ok(())
+ }
}
diff --git a/virtualizationmanager/src/crosvm.rs b/virtualizationmanager/src/crosvm.rs
index bb6066f..b842574 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_vendor: Option<File>,
}
/// A disk image to pass to crosvm for a VM.
@@ -886,6 +887,8 @@
.arg("--socket")
.arg(add_preserved_fd(&mut preserved_fds, &control_server_socket.as_raw_descriptor()));
+ // TODO(b/285855436): Pass dtbo_vendor after --device-tree-overlay crosvm option is supported.
+
append_platform_devices(&mut command, &config)?;
debug!("Preserving FDs {:?}", preserved_fds);
diff --git a/vmbase/src/heap.rs b/vmbase/src/heap.rs
index ec03d38..99c06aa 100644
--- a/vmbase/src/heap.rs
+++ b/vmbase/src/heap.rs
@@ -86,6 +86,21 @@
}
#[no_mangle]
+unsafe extern "C" fn __memset_chk(
+ dest: *mut c_void,
+ val: u8,
+ len: usize,
+ destlen: usize,
+) -> *mut c_void {
+ assert!(len <= destlen, "memset buffer overflow detected");
+ // SAFETY: `dest` is valid for writes of `len` bytes.
+ unsafe {
+ ptr::write_bytes(dest, val, len);
+ }
+ dest
+}
+
+#[no_mangle]
/// SAFETY: ptr must be null or point to a currently-allocated block returned by allocate (either
/// directly or via malloc or calloc). Note that this function is called directly from C, so we have
/// to trust that the C code is doing the right thing; there are checks below which will catch some