Merge changes I7d25f88d,Ie71a8f57 into main
* changes:
libhyp: Make DeviceAssigningHypervisor pub
pvmfw: Support #iommu-cells = <1>
diff --git a/authfs/service/src/main.rs b/authfs/service/src/main.rs
index 78de07a..67e22a5 100644
--- a/authfs/service/src/main.rs
+++ b/authfs/service/src/main.rs
@@ -127,6 +127,7 @@
Ok(unsafe { OwnedFd::from_raw_fd(raw_fd) })
}
+#[allow(clippy::eq_op)]
fn try_main() -> Result<()> {
let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
let log_level = if debuggable { log::Level::Trace } else { log::Level::Info };
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index b558f06..9f6ce9c 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -31,6 +31,7 @@
use std::panic;
use std::sync::Arc;
+#[allow(clippy::eq_op)]
fn try_main() -> Result<()> {
let debuggable = env!("TARGET_BUILD_VARIANT") != "user";
let log_level = if debuggable { log::Level::Debug } else { log::Level::Info };
diff --git a/libs/apexutil/Android.bp b/libs/apexutil/Android.bp
index f9b72c4..beff58d 100644
--- a/libs/apexutil/Android.bp
+++ b/libs/apexutil/Android.bp
@@ -9,7 +9,9 @@
srcs: ["src/lib.rs"],
edition: "2021",
rustlibs: [
+ "libapex_manifest_rs",
"liblog_rust",
+ "libprotobuf",
"libthiserror",
"libvbmeta_rust",
"libzip",
@@ -26,7 +28,12 @@
defaults: ["libapexutil_rust.defaults"],
prefer_rlib: true,
test_suites: ["general-tests"],
- data: ["tests/data/*"],
+ // We're reusing test APEXes from system/apex/apexd
+ data: [
+ ":apex.apexd_test",
+ ":apex.apexd_test_v2_no_pb",
+ ":gen_key_mismatch_with_image_apex",
+ ],
rustlibs: [
"libhex",
],
diff --git a/libs/apexutil/src/lib.rs b/libs/apexutil/src/lib.rs
index 8a934e2..639135f 100644
--- a/libs/apexutil/src/lib.rs
+++ b/libs/apexutil/src/lib.rs
@@ -14,6 +14,8 @@
//! Routines for handling APEX payload
+use apex_manifest::apex_manifest::ApexManifest;
+use protobuf::Message;
use std::fs::File;
use std::io::{self, Read};
use thiserror::Error;
@@ -23,28 +25,32 @@
const APEX_PUBKEY_ENTRY: &str = "apex_pubkey";
const APEX_PAYLOAD_ENTRY: &str = "apex_payload.img";
+const APEX_MANIFEST_ENTRY: &str = "apex_manifest.pb";
/// Errors from parsing an APEX.
#[derive(Debug, Error)]
pub enum ApexParseError {
/// There was an IO error.
- #[error("IO error")]
+ #[error("IO error: {0}")]
Io(#[from] io::Error),
/// The Zip archive was invalid.
- #[error("Cannot read zip archive")]
+ #[error("Cannot read zip archive: {0}")]
InvalidZip(&'static str),
- /// The apex_pubkey file was missing from the APEX.
- #[error("APEX doesn't contain apex_pubkey")]
- PubkeyMissing,
- /// The apex_payload.img file was missing from the APEX.
- #[error("APEX doesn't contain apex_payload.img")]
- PayloadMissing,
+ /// An expected file was missing from the APEX.
+ #[error("APEX doesn't contain {0}")]
+ MissingFile(&'static str),
/// There was no hashtree descriptor in the APEX payload's VBMeta image.
#[error("Non-hashtree descriptor found in payload's VBMeta image")]
DescriptorNotHashtree,
/// There was an error parsing the APEX payload's VBMeta image.
- #[error("Could not parse payload's VBMeta image")]
+ #[error("Could not parse payload's VBMeta image: {0}")]
PayloadVbmetaError(#[from] vbmeta::VbMetaImageParseError),
+ /// Data was missing from the VBMeta
+ #[error("Data missing from VBMeta: {0}")]
+ VbmetaMissingData(&'static str),
+ /// An error occurred parsing the APEX manifest as a protobuf
+ #[error("Error parsing manifest protobuf: {0}")]
+ ManifestProtobufError(#[from] protobuf::Error),
}
/// Errors from verifying an APEX.
@@ -58,29 +64,44 @@
PayloadVbmetaError(#[from] vbmeta::VbMetaImageVerificationError),
/// The APEX payload was not verified with the apex_pubkey.
#[error("APEX pubkey mismatch")]
- ApexPubkeyMistmatch,
+ ApexPubkeyMismatch,
}
-/// Verification result holds public key and root digest of apex_payload.img
+/// Information extracted from the APEX during AVB verification.
+#[derive(Debug)]
pub struct ApexVerificationResult {
+ /// The name of the APEX, from its manifest.
+ pub name: Option<String>,
+ /// The version of the APEX, from its manifest.
+ pub version: Option<i64>,
/// The public key that verifies the payload signature.
pub public_key: Vec<u8>,
/// The root digest of the payload hashtree.
pub root_digest: Vec<u8>,
}
-/// Verify APEX payload by AVB verification and return public key and root digest
+/// Verify APEX payload by AVB verification and return information about the APEX.
+/// This verifies that the VBMeta is correctly signed by the public key specified in the APEX.
+/// It doesn't verify that that is the correct key, nor does it verify that the payload matches
+/// the signed root hash - that is handled by dm-verity once apexd has mounted the APEX.
pub fn verify(path: &str) -> Result<ApexVerificationResult, ApexVerificationError> {
let apex_file = File::open(path).map_err(ApexParseError::Io)?;
- let (public_key, image_offset, image_size) = get_public_key_and_image_info(&apex_file)?;
+ let ApexZipInfo { public_key, image_offset, image_size, manifest } =
+ get_apex_zip_info(&apex_file)?;
let vbmeta = VbMetaImage::verify_reader_region(apex_file, image_offset, image_size)?;
let root_digest = find_root_digest(&vbmeta)?;
- match vbmeta.public_key() {
- Some(payload_public_key) if public_key == payload_public_key => {
- Ok(ApexVerificationResult { public_key, root_digest })
- }
- _ => Err(ApexVerificationError::ApexPubkeyMistmatch),
+ let vbmeta_public_key =
+ vbmeta.public_key().ok_or(ApexParseError::VbmetaMissingData("public key"))?;
+ if vbmeta_public_key != public_key {
+ return Err(ApexVerificationError::ApexPubkeyMismatch);
}
+ let (name, version) = if cfg!(dice_changes) {
+ let ApexManifestInfo { name, version } = decode_manifest(&manifest)?;
+ (Some(name), Some(version))
+ } else {
+ (None, None)
+ };
+ Ok(ApexVerificationResult { name, version, public_key, root_digest })
}
fn find_root_digest(vbmeta: &VbMetaImage) -> Result<Vec<u8>, ApexParseError> {
@@ -93,46 +114,52 @@
Err(ApexParseError::DescriptorNotHashtree)
}
-/// Gets the hash of the payload's verified VBMeta image data.
-pub fn get_payload_vbmeta_image_hash(path: &str) -> Result<Vec<u8>, ApexVerificationError> {
- let apex_file = File::open(path).map_err(ApexParseError::Io)?;
- let (_, offset, size) = get_public_key_and_image_info(&apex_file)?;
- let vbmeta = VbMetaImage::verify_reader_region(apex_file, offset, size)?;
- Ok(vbmeta.hash().ok_or(ApexVerificationError::ApexPubkeyMistmatch)?.to_vec())
+struct ApexZipInfo {
+ public_key: Vec<u8>,
+ image_offset: u64,
+ image_size: u64,
+ manifest: Vec<u8>,
}
-fn get_public_key_and_image_info(apex_file: &File) -> Result<(Vec<u8>, u64, u64), ApexParseError> {
- let mut z = ZipArchive::new(apex_file).map_err(|err| match err {
- ZipError::Io(err) => ApexParseError::Io(err),
- ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
- ApexParseError::InvalidZip(s)
- }
- ZipError::FileNotFound => unreachable!(),
- })?;
+fn get_apex_zip_info(apex_file: &File) -> Result<ApexZipInfo, ApexParseError> {
+ let mut z = ZipArchive::new(apex_file).map_err(|err| from_zip_error(err, "?"))?;
let mut public_key = Vec::new();
z.by_name(APEX_PUBKEY_ENTRY)
- .map_err(|err| match err {
- ZipError::Io(err) => ApexParseError::Io(err),
- ZipError::FileNotFound => ApexParseError::PubkeyMissing,
- ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
- ApexParseError::InvalidZip(s)
- }
- })?
+ .map_err(|err| from_zip_error(err, APEX_PUBKEY_ENTRY))?
.read_to_end(&mut public_key)?;
let (image_offset, image_size) = z
.by_name(APEX_PAYLOAD_ENTRY)
.map(|f| (f.data_start(), f.size()))
- .map_err(|err| match err {
- ZipError::Io(err) => ApexParseError::Io(err),
- ZipError::FileNotFound => ApexParseError::PayloadMissing,
- ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
- ApexParseError::InvalidZip(s)
- }
- })?;
+ .map_err(|err| from_zip_error(err, APEX_PAYLOAD_ENTRY))?;
- Ok((public_key, image_offset, image_size))
+ let mut manifest = Vec::new();
+ z.by_name(APEX_MANIFEST_ENTRY)
+ .map_err(|err| from_zip_error(err, APEX_MANIFEST_ENTRY))?
+ .read_to_end(&mut manifest)?;
+
+ Ok(ApexZipInfo { public_key, image_offset, image_size, manifest })
+}
+
+struct ApexManifestInfo {
+ name: String,
+ version: i64,
+}
+
+fn decode_manifest(mut manifest: &[u8]) -> Result<ApexManifestInfo, ApexParseError> {
+ let manifest = ApexManifest::parse_from_reader(&mut manifest)?;
+ Ok(ApexManifestInfo { name: manifest.name, version: manifest.version })
+}
+
+fn from_zip_error(err: ZipError, name: &'static str) -> ApexParseError {
+ match err {
+ ZipError::Io(err) => ApexParseError::Io(err),
+ ZipError::InvalidArchive(s) | ZipError::UnsupportedArchive(s) => {
+ ApexParseError::InvalidZip(s)
+ }
+ ZipError::FileNotFound => ApexParseError::MissingFile(name),
+ }
}
#[cfg(test)]
@@ -141,20 +168,68 @@
#[test]
fn apex_verification_returns_valid_result() {
- let res = verify("tests/data/test.apex").unwrap();
- // The expected hex is generated when we ran the method the first time.
+ let res = verify("apex.apexd_test.apex").unwrap();
+ let (expected_name, expected_version) = if cfg!(dice_changes) {
+ (Some("com.android.apex.test_package"), Some(1))
+ } else {
+ (None, None)
+ };
+ assert_eq!(res.name.as_deref(), expected_name);
+ assert_eq!(res.version, expected_version);
+ // The expected hex values were generated when we ran the method the first time.
assert_eq!(
hex::encode(res.root_digest),
- "fe11ab17da0a3a738b54bdc3a13f6139cbdf91ec32f001f8d4bbbf8938e04e39"
+ "54265da77ae1fd619e39809ad99fedc576bb20c0c7a8002190fa64438436299f"
+ );
+ assert_eq!(
+ hex::encode(res.public_key),
+ "\
+ 00001000963a5527aaf0145b3bb5f899a05034ccc76dafdd671dbf4e42c04df2eeba15\
+ 6c884816d7d08ef8d834d4adc27979afed9eaf406694d0d600f0b6d31e3ab85da47d27\
+ 9c223a1630e02332d920587617ea766a136057a3a3232a7c42f83fb3763e853be4026c\
+ 067524a95fcbfcc6caadfb553210bb5385f5adc5caeb0e3f6a9aa56af88d8899d962eb\
+ 807864feabeeacdd868697935fb4cc4843957e0d90ee4293c715c4e5b970e6545a17d1\
+ 735f814c7d4dbdeaac97275a84f292e3715c158d38eb00eebd010dd2fa56595c0e5627\
+ 06c7a94e566912f993e5e35c04b2a314d1bce1ceb10de6c50f8101ddb6ee993fc79959\
+ 2e79ee73b77741ee5c076c89343684344a6d080e5529a046d506d104bf32903e39c363\
+ b020fee9d87e7c6ffdad120b630386e958416ac156bc2d7301836c79e926e8f185a640\
+ be05135e17018c88dde02cd7bd49655e9e9dff7f965fb8e68217236c18d23b6d7e7632\
+ 184acb95b088598601c809d5e66c19f5e06b5e5ff1bbae7e3142959d9380db2d4a25c8\
+ 757975232ea311016e830703a6023b0986e885f2eda066517fce09f33f359b6ef7cc5a\
+ 2fdaced74257661bad184a653ea2d80d1af68de5821c06a472635f0276dc42d699f588\
+ ea6c46189ca1ad544bbd4951a766bc4119b0ea671cb16556762721723bf1db47c83c76\
+ a7cc2fd3b6029efec9908d9d4640294f6ea46f6e1a3195e9252c393e35698911a7c496\
+ 138dc2dd8d9dcb470ae1c6d2224d13b160fb3ae4bc235f6133c2ff5f9232fb89adfdba\
+ 48dcc47cf29a22cd47dcec0b1a179f352c9848a8e04ac37f35777a24312c821febc591\
+ 84c8cdefc88e50b4d6bc9530ca743f4284c9773677d38527e6e8020fe367f0f16a6c49\
+ 9a7f2da95ec6471f7382e5c0da98b531702cb55a560de7cafc7b6111aae0f896fb1fed\
+ d4997a954c6c083ef1fd3bb13fef3f95022523fb1fbe7f4a49e12e54a5206f95daa316\
+ ac009b7bee4039f769fd28033db6013df841c86d8345d44418fbc9f669e4ee3294b2ff\
+ 29d048f53d768c0a41f9a280f0229d9912e8b2fb734617a9947be973ed1dc7bdeac9e2\
+ 6028d59317098a44bacdb3b10ccde6ef02f7c94124461032a033701ce523b13142658c\
+ 265385198903ccf227ad5ae88ec31e586cd8f855641fd2646dba8053d0d0924f132505\
+ 8141f1c7433aa9686f48e3f3a972b56776eaf8bf22a740d1aea2ef473184d697de1dab\
+ 9b62a227611c7500b11dea2e5eb8051807c0d1f2fe032acfd7701c017e629f99c74de5\
+ da4c2a542f17b9833beb14442aa7c2990b828473376ea03fdb4a650b88e821fe5026e8\
+ ffb7002d095c9877ee3a98a4488ed3287e9be4942a223f4e32bc26c2ebd02eec20dc82\
+ 7493b44f4efaf9b2e175d4de2b07c32d6d359e234c9e50ef905ffa7f6907c313a3c9f4\
+ 40d1efd5ec7cbeef06dcfd649f4c8219ad"
);
}
#[test]
- fn payload_vbmeta_has_valid_image_hash() {
- let result = get_payload_vbmeta_image_hash("tests/data/test.apex").unwrap();
- assert_eq!(
- hex::encode(result),
- "296e32a76544de9da01713e471403ab4667705ad527bb4f1fac0cf61e7ce122d"
- );
+ fn apex_no_manifest_fails_verification() {
+ match verify("apex.apexd_test_v2_no_pb.apex").unwrap_err() {
+ ApexVerificationError::ParseError(ApexParseError::MissingFile(_)) => (),
+ e => panic!("Unexpected error {e}"),
+ }
+ }
+
+ #[test]
+ fn apex_signature_mismatch_fails_verification() {
+ match verify("apex.apexd_test_wrong_public_key.apex").unwrap_err() {
+ ApexVerificationError::ApexPubkeyMismatch => (),
+ e => panic!("Unexpected error {e}"),
+ }
}
}
diff --git a/libs/apexutil/tests/data/README.md b/libs/apexutil/tests/data/README.md
deleted file mode 100644
index 82ebec6..0000000
--- a/libs/apexutil/tests/data/README.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# Test data
-
-- test.apex: copied from system/apexshim/prebuilts/x86/com.android.apex.cts.shim.v1.apex
\ No newline at end of file
diff --git a/libs/apexutil/tests/data/test.apex b/libs/apexutil/tests/data/test.apex
deleted file mode 100644
index fd79365..0000000
--- a/libs/apexutil/tests/data/test.apex
+++ /dev/null
Binary files differ
diff --git a/libs/bssl/src/ec_key.rs b/libs/bssl/src/ec_key.rs
index 6436be3..a187259 100644
--- a/libs/bssl/src/ec_key.rs
+++ b/libs/bssl/src/ec_key.rs
@@ -102,6 +102,7 @@
EC_KEY_set_public_key_affine_coordinates(ec_key.0.as_ptr(), x.as_ref(), y.as_ref())
};
check_int_result(ret, ApiName::EC_KEY_set_public_key_affine_coordinates)?;
+ ec_key.check_key()?;
Ok(ec_key)
}
diff --git a/libs/dice/open_dice/src/bcc.rs b/libs/dice/open_dice/src/bcc.rs
index 199e1a9..9c9545b 100644
--- a/libs/dice/open_dice/src/bcc.rs
+++ b/libs/dice/open_dice/src/bcc.rs
@@ -20,7 +20,7 @@
DiceAndroidConfigValues, DiceAndroidFormatConfigDescriptor, DiceAndroidHandoverMainFlow,
DiceAndroidHandoverParse, DiceAndroidMainFlow, DICE_ANDROID_CONFIG_COMPONENT_NAME,
DICE_ANDROID_CONFIG_COMPONENT_VERSION, DICE_ANDROID_CONFIG_RESETTABLE,
- DICE_ANDROID_CONFIG_SECURITY_VERSION,
+ DICE_ANDROID_CONFIG_RKP_VM_MARKER, DICE_ANDROID_CONFIG_SECURITY_VERSION,
};
use std::{ffi::CStr, ptr};
@@ -36,6 +36,8 @@
pub resettable: bool,
/// Monotonically increasing version of the component.
pub security_version: Option<u64>,
+ /// Whether the component can take part in running the RKP VM.
+ pub rkp_vm_marker: bool,
}
/// Formats a configuration descriptor following the Android Profile for DICE specification.
@@ -58,6 +60,9 @@
configs |= DICE_ANDROID_CONFIG_SECURITY_VERSION;
version
});
+ if values.rkp_vm_marker {
+ configs |= DICE_ANDROID_CONFIG_RKP_VM_MARKER;
+ }
let values =
DiceAndroidConfigValues { configs, component_name, component_version, security_version };
diff --git a/microdroid/payload/metadata.proto b/microdroid/payload/metadata.proto
index 6b999af..b03d466 100644
--- a/microdroid/payload/metadata.proto
+++ b/microdroid/payload/metadata.proto
@@ -37,14 +37,18 @@
}
message ApexPayload {
+ // Next id: 9
+
// Required.
string name = 1;
string partition_name = 2;
// Optional.
- // When specified, apex payload should be verified with the public key and root digest.
+ // When specified, apex payload should be verified against these values.
bytes public_key = 3;
bytes root_digest = 4;
+ int64 manifest_version = 7;
+ string manifest_name = 8;
// Required.
// The timestamp in seconds when the APEX was last updated. This should match the value in
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 6b0775a..e6ddfb9 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -13,7 +13,7 @@
// limitations under the License.
use crate::dice_driver::DiceDriver;
-use crate::instance::ApkData;
+use crate::instance::{ApexData, ApkData};
use crate::{is_debuggable, MicrodroidData};
use anyhow::{bail, Context, Result};
use ciborium::{cbor, Value};
@@ -26,24 +26,23 @@
/// Perform an open DICE derivation for the payload.
pub fn dice_derivation(
dice: DiceDriver,
- verified_data: &MicrodroidData,
+ instance_data: &MicrodroidData,
payload_metadata: &PayloadMetadata,
) -> Result<OwnedDiceArtifacts> {
- let subcomponents = build_subcomponent_list(verified_data);
-
+ let subcomponents = build_subcomponent_list(instance_data);
let config_descriptor = format_payload_config_descriptor(payload_metadata, &subcomponents)
.context("Building config descriptor")?;
// Calculate compound digests of code and authorities
let mut code_hash_ctx = Sha512::new();
let mut authority_hash_ctx = Sha512::new();
- code_hash_ctx.update(verified_data.apk_data.root_hash.as_ref());
- authority_hash_ctx.update(verified_data.apk_data.pubkey.as_ref());
- for extra_apk in &verified_data.extra_apks_data {
+ code_hash_ctx.update(instance_data.apk_data.root_hash.as_ref());
+ authority_hash_ctx.update(instance_data.apk_data.pubkey.as_ref());
+ for extra_apk in &instance_data.extra_apks_data {
code_hash_ctx.update(extra_apk.root_hash.as_ref());
authority_hash_ctx.update(extra_apk.pubkey.as_ref());
}
- for apex in &verified_data.apex_data {
+ for apex in &instance_data.apex_data {
code_hash_ctx.update(apex.root_digest.as_ref());
authority_hash_ctx.update(apex.public_key.as_ref());
}
@@ -54,7 +53,7 @@
let debuggable = is_debuggable()?;
// Send the details to diced
- let hidden = verified_data.salt.clone().try_into().unwrap();
+ let hidden = instance_data.salt.clone().try_into().unwrap();
dice.derive(code_hash, &config_descriptor, authority_hash, debuggable, hidden)
}
@@ -85,17 +84,29 @@
Box::new(sha512(&apk.pubkey)),
}
}
+
+ fn for_apex(apex: &'a ApexData) -> Self {
+ // Note that this is only reachable if the dice_changes flag is on, in which case
+ // the manifest data will always be present.
+ Self {
+ name: format!("apex:{}", apex.manifest_name.as_ref().unwrap()),
+ version: apex.manifest_version.unwrap() as u64,
+ code_hash: &apex.root_digest,
+ authority_hash: Box::new(sha512(&apex.public_key)),
+ }
+ }
}
-fn build_subcomponent_list(verified_data: &MicrodroidData) -> Vec<Subcomponent> {
+fn build_subcomponent_list(instance_data: &MicrodroidData) -> Vec<Subcomponent> {
if !cfg!(dice_changes) {
return vec![];
}
- once(&verified_data.apk_data)
- .chain(&verified_data.extra_apks_data)
- .map(Subcomponent::for_apk)
- .collect()
+ let apks = once(&instance_data.apk_data)
+ .chain(&instance_data.extra_apks_data)
+ .map(Subcomponent::for_apk);
+ let apexes = instance_data.apex_data.iter().map(Subcomponent::for_apex);
+ apks.chain(apexes).collect()
}
// Returns a configuration descriptor of the given payload. See vm_config.cddl for a definition
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index 6c9e245..b0fc03d 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -304,6 +304,8 @@
#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)]
pub struct ApexData {
pub name: String,
+ pub manifest_name: Option<String>,
+ pub manifest_version: Option<i64>,
pub public_key: Vec<u8>,
pub root_digest: Vec<u8>,
pub last_update_seconds: u64,
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 1b41e58..9e167a4 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -246,20 +246,20 @@
}
// Verify the payload before using it.
- let verified_data = verify_payload(&metadata, saved_data.as_ref())
+ let extracted_data = verify_payload(&metadata, saved_data.as_ref())
.context("Payload verification failed")
.map_err(|e| MicrodroidError::PayloadVerificationFailed(e.to_string()))?;
// In case identity is ignored (by debug policy), we should reuse existing payload data, even
// when the payload is changed. This is to keep the derived secret same as before.
- let verified_data = if let Some(saved_data) = saved_data {
+ let instance_data = if let Some(saved_data) = saved_data {
if !is_verified_boot() {
- if saved_data != verified_data {
+ if saved_data != extracted_data {
info!("Detected an update of the payload, but continue (regarding debug policy)")
}
} else {
ensure!(
- saved_data == verified_data,
+ saved_data == extracted_data,
MicrodroidError::PayloadChanged(String::from(
"Detected an update of the payload which isn't supported yet."
))
@@ -270,9 +270,9 @@
} else {
info!("Saving verified data.");
instance
- .write_microdroid_data(&verified_data, &dice)
+ .write_microdroid_data(&extracted_data, &dice)
.context("Failed to write identity data")?;
- verified_data
+ extracted_data
};
let payload_metadata = metadata.payload.ok_or_else(|| {
@@ -281,7 +281,7 @@
// To minimize the exposure to untrusted data, derive dice profile as soon as possible.
info!("DICE derivation for payload");
- let dice_artifacts = dice_derivation(dice, &verified_data, &payload_metadata)?;
+ let dice_artifacts = dice_derivation(dice, &instance_data, &payload_metadata)?;
let vm_secret = VmSecret::new(dice_artifacts).context("Failed to create VM secrets")?;
if cfg!(dice_changes) {
@@ -326,10 +326,10 @@
.ok_or_else(|| MicrodroidError::PayloadInvalidConfig("No task in VM config".to_string()))?;
ensure!(
- config.extra_apks.len() == verified_data.extra_apks_data.len(),
+ config.extra_apks.len() == instance_data.extra_apks_data.len(),
"config expects {} extra apks, but found {}",
config.extra_apks.len(),
- verified_data.extra_apks_data.len()
+ instance_data.extra_apks_data.len()
);
mount_extra_apks(&config, &mut zipfuse)?;
diff --git a/microdroid_manager/src/payload.rs b/microdroid_manager/src/payload.rs
index a553ce4..98fe24b 100644
--- a/microdroid_manager/src/payload.rs
+++ b/microdroid_manager/src/payload.rs
@@ -17,8 +17,7 @@
use crate::instance::ApexData;
use crate::ioutil::wait_for_file;
use anyhow::Result;
-use apexutil::verify;
-use log::info;
+use log::{info, warn};
use microdroid_metadata::{read_metadata, ApexPayload, Metadata};
use std::time::Duration;
@@ -38,13 +37,19 @@
.apexes
.iter()
.map(|apex| {
- let name = apex.name.clone();
let apex_path = format!("/dev/block/by-name/{}", apex.partition_name);
- let result = verify(&apex_path)?;
+ let extracted = apexutil::verify(&apex_path)?;
+ if let Some(manifest_name) = &extracted.name {
+ if &apex.name != manifest_name {
+ warn!("Apex named {} is named {} in its manifest", apex.name, manifest_name);
+ }
+ };
Ok(ApexData {
- name,
- public_key: result.public_key,
- root_digest: result.root_digest,
+ name: apex.name.clone(),
+ manifest_name: extracted.name,
+ manifest_version: extracted.version,
+ public_key: extracted.public_key,
+ root_digest: extracted.root_digest,
last_update_seconds: apex.last_update_seconds,
is_factory: apex.is_factory,
})
@@ -61,6 +66,8 @@
name: data.name.clone(),
public_key: data.public_key.clone(),
root_digest: data.root_digest.clone(),
+ manifest_name: data.manifest_name.clone().unwrap_or_default(),
+ manifest_version: data.manifest_version.unwrap_or_default(),
last_update_seconds: data.last_update_seconds,
is_factory: data.is_factory,
..Default::default()
diff --git a/pvmfw/Android.bp b/pvmfw/Android.bp
index 103619f..f49bbce 100644
--- a/pvmfw/Android.bp
+++ b/pvmfw/Android.bp
@@ -45,22 +45,69 @@
cmd: "touch $(out)",
}
-rust_test {
- name: "libpvmfw.bootargs.test",
- host_supported: true,
- // For now, only bootargs.rs is written to be conditionally compiled with std.
- srcs: ["src/bootargs.rs"],
+rust_defaults {
+ name: "libpvmfw.test.defaults",
defaults: ["avf_build_flags_rust"],
test_suites: ["general-tests"],
test_options: {
unit_test: true,
},
+ prefer_rlib: true,
rustlibs: [
"libcstr",
+ ],
+}
+
+rust_test {
+ name: "libpvmfw.bootargs.test",
+ host_supported: true,
+ // For now, only bootargs.rs is written to be conditionally compiled with std.
+ srcs: ["src/bootargs.rs"],
+ defaults: ["libpvmfw.test.defaults"],
+ rustlibs: [
"libzeroize",
],
}
+rust_test {
+ name: "libpvmfw.device_assignment.test",
+ srcs: ["src/device_assignment.rs"],
+ defaults: ["libpvmfw.test.defaults"],
+ rustlibs: [
+ "liblibfdt",
+ "liblog_rust",
+ "libpvmfw_fdt_template",
+ ],
+ data: [
+ ":test_pvmfw_devices_vm_dtbo",
+ ":test_pvmfw_devices_vm_dtbo_without_symbols",
+ ":test_pvmfw_devices_with_rng",
+ ":test_pvmfw_devices_with_rng_iommu",
+ ":test_pvmfw_devices_with_multiple_devices_iommus",
+ ":test_pvmfw_devices_with_iommu_sharing",
+ ":test_pvmfw_devices_with_iommu_id_conflict",
+ ],
+ // To use libpvmfw_fdt_template for testing
+ enabled: false,
+ target: {
+ android_arm64: {
+ enabled: true,
+ },
+ },
+}
+
+rust_test {
+ name: "libpvmfw.dice.test",
+ srcs: ["src/dice.rs"],
+ defaults: ["libpvmfw.test.defaults"],
+ rustlibs: [
+ "libcbor_util",
+ "libciborium",
+ "libdiced_open_dice_nostd",
+ "libpvmfw_avb_nostd",
+ ],
+}
+
genrule {
name: "test_pvmfw_devices_vm_dtbo",
defaults: ["dts_to_dtb"],
@@ -110,39 +157,6 @@
out: ["test_pvmfw_devices_with_iommu_id_conflict.dtb"],
}
-rust_test {
- name: "libpvmfw.device_assignment.test",
- srcs: ["src/device_assignment.rs"],
- defaults: ["avf_build_flags_rust"],
- test_suites: ["general-tests"],
- test_options: {
- unit_test: true,
- },
- prefer_rlib: true,
- rustlibs: [
- "libcstr",
- "liblibfdt",
- "liblog_rust",
- "libpvmfw_fdt_template",
- ],
- data: [
- ":test_pvmfw_devices_vm_dtbo",
- ":test_pvmfw_devices_vm_dtbo_without_symbols",
- ":test_pvmfw_devices_with_rng",
- ":test_pvmfw_devices_with_rng_iommu",
- ":test_pvmfw_devices_with_multiple_devices_iommus",
- ":test_pvmfw_devices_with_iommu_sharing",
- ":test_pvmfw_devices_with_iommu_id_conflict",
- ],
- // To use libpvmfw_fdt_template for testing
- enabled: false,
- target: {
- android_arm64: {
- enabled: true,
- },
- },
-}
-
cc_binary {
name: "pvmfw",
defaults: ["vmbase_elf_defaults"],
diff --git a/pvmfw/TEST_MAPPING b/pvmfw/TEST_MAPPING
index f21318e..e948400 100644
--- a/pvmfw/TEST_MAPPING
+++ b/pvmfw/TEST_MAPPING
@@ -10,6 +10,9 @@
},
{
"name" : "libpvmfw.device_assignment.test"
+ },
+ {
+ "name" : "libpvmfw.dice.test"
}
]
}
diff --git a/pvmfw/avb/src/descriptor/collection.rs b/pvmfw/avb/src/descriptor/collection.rs
index f47bfbd..6784758 100644
--- a/pvmfw/avb/src/descriptor/collection.rs
+++ b/pvmfw/avb/src/descriptor/collection.rs
@@ -18,11 +18,11 @@
use super::hash::HashDescriptor;
use super::property::PropertyDescriptor;
use crate::partition::PartitionName;
-use crate::utils::{self, is_not_null, to_usize, usize_checked_add};
+use crate::utils::{to_usize, usize_checked_add};
use crate::PvmfwVerifyError;
+use avb::{IoError, IoResult, SlotVerifyError, SlotVerifyNoDataResult, VbmetaData};
use avb_bindgen::{
avb_descriptor_foreach, avb_descriptor_validate_and_byteswap, AvbDescriptor, AvbDescriptorTag,
- AvbVBMetaData,
};
use core::{ffi::c_void, mem::size_of, slice};
use tinyvec::ArrayVec;
@@ -36,24 +36,16 @@
}
impl<'a> Descriptors<'a> {
- /// Builds `Descriptors` from `AvbVBMetaData`.
- /// Returns an error if the given `AvbVBMetaData` contains non-hash descriptor, hash
+ /// Builds `Descriptors` from `VbmetaData`.
+ /// Returns an error if the given `VbmetaData` contains non-hash descriptor, hash
/// descriptor of unknown `PartitionName` or duplicated hash descriptors.
- ///
- /// # Safety
- ///
- /// Behavior is undefined if any of the following conditions are violated:
- /// * `vbmeta.vbmeta_data` must be non-null and points to a valid VBMeta.
- /// * `vbmeta.vbmeta_data` must be valid for reading `vbmeta.vbmeta_size` bytes.
- pub(crate) unsafe fn from_vbmeta(vbmeta: AvbVBMetaData) -> Result<Self, PvmfwVerifyError> {
- is_not_null(vbmeta.vbmeta_data).map_err(|_| avb::SlotVerifyError::Io)?;
- let mut res: Result<Self, avb::IoError> = Ok(Self::default());
- // SAFETY: It is safe as the raw pointer `vbmeta.vbmeta_data` is a non-null pointer and
- // points to a valid VBMeta structure.
+ pub(crate) fn from_vbmeta(vbmeta: &'a VbmetaData) -> Result<Self, PvmfwVerifyError> {
+ let mut res: IoResult<Self> = Ok(Self::default());
+ // SAFETY: It is safe as `vbmeta.data()` contains a valid VBMeta structure.
let output = unsafe {
avb_descriptor_foreach(
- vbmeta.vbmeta_data,
- vbmeta.vbmeta_size,
+ vbmeta.data().as_ptr(),
+ vbmeta.data().len(),
Some(check_and_save_descriptor),
&mut res as *mut _ as *mut c_void,
)
@@ -61,7 +53,7 @@
if output == res.is_ok() {
res.map_err(PvmfwVerifyError::InvalidDescriptors)
} else {
- Err(avb::SlotVerifyError::InvalidMetadata.into())
+ Err(SlotVerifyError::InvalidMetadata.into())
}
}
@@ -74,11 +66,11 @@
pub(crate) fn find_hash_descriptor(
&self,
partition_name: PartitionName,
- ) -> Result<&HashDescriptor, avb::SlotVerifyError> {
+ ) -> SlotVerifyNoDataResult<&HashDescriptor> {
self.hash_descriptors
.iter()
.find(|d| d.partition_name == partition_name)
- .ok_or(avb::SlotVerifyError::InvalidMetadata)
+ .ok_or(SlotVerifyError::InvalidMetadata)
}
pub(crate) fn has_property_descriptor(&self) -> bool {
@@ -89,27 +81,24 @@
self.prop_descriptor.as_ref().filter(|desc| desc.key == key).map(|desc| desc.value)
}
- fn push(&mut self, descriptor: Descriptor<'a>) -> utils::Result<()> {
+ fn push(&mut self, descriptor: Descriptor<'a>) -> IoResult<()> {
match descriptor {
Descriptor::Hash(d) => self.push_hash_descriptor(d),
Descriptor::Property(d) => self.push_property_descriptor(d),
}
}
- fn push_hash_descriptor(&mut self, descriptor: HashDescriptor<'a>) -> utils::Result<()> {
+ fn push_hash_descriptor(&mut self, descriptor: HashDescriptor<'a>) -> IoResult<()> {
if self.hash_descriptors.iter().any(|d| d.partition_name == descriptor.partition_name) {
- return Err(avb::IoError::Io);
+ return Err(IoError::Io);
}
self.hash_descriptors.push(descriptor);
Ok(())
}
- fn push_property_descriptor(
- &mut self,
- descriptor: PropertyDescriptor<'a>,
- ) -> utils::Result<()> {
+ fn push_property_descriptor(&mut self, descriptor: PropertyDescriptor<'a>) -> IoResult<()> {
if self.prop_descriptor.is_some() {
- return Err(avb::IoError::Io);
+ return Err(IoError::Io);
}
self.prop_descriptor.replace(descriptor);
Ok(())
@@ -120,8 +109,7 @@
///
/// Behavior is undefined if any of the following conditions are violated:
/// * The `descriptor` pointer must be non-null and points to a valid `AvbDescriptor` struct.
-/// * The `user_data` pointer must be non-null, points to a valid
-/// `Result<Descriptors, avb::IoError>`
+/// * The `user_data` pointer must be non-null, points to a valid `IoResult<Descriptors>`
/// struct and is initialized.
unsafe extern "C" fn check_and_save_descriptor(
descriptor: *const AvbDescriptor,
@@ -129,8 +117,7 @@
) -> bool {
// SAFETY: It is safe because the caller ensures that `user_data` points to a valid struct and
// is initialized.
- let Some(res) = (unsafe { (user_data as *mut Result<Descriptors, avb::IoError>).as_mut() })
- else {
+ let Some(res) = (unsafe { (user_data as *mut IoResult<Descriptors>).as_mut() }) else {
return false;
};
let Ok(descriptors) = res else {
@@ -154,7 +141,7 @@
unsafe fn try_check_and_save_descriptor(
descriptor: *const AvbDescriptor,
descriptors: &mut Descriptors,
-) -> utils::Result<()> {
+) -> IoResult<()> {
// SAFETY: It is safe because the caller ensures that `descriptor` is a non-null pointer
// pointing to a valid struct.
let descriptor = unsafe { Descriptor::from_descriptor_ptr(descriptor)? };
@@ -171,7 +158,7 @@
///
/// Behavior is undefined if any of the following conditions are violated:
/// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
- unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> utils::Result<Self> {
+ unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> IoResult<Self> {
let avb_descriptor =
// SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
// a valid `AvbDescriptor`.
@@ -197,7 +184,7 @@
unsafe { PropertyDescriptor::from_descriptor_ptr(descriptor, data)? };
Ok(Self::Property(descriptor))
}
- _ => Err(avb::IoError::NoSuchValue),
+ _ => Err(IoError::NoSuchValue),
}
}
}
diff --git a/pvmfw/avb/src/descriptor/common.rs b/pvmfw/avb/src/descriptor/common.rs
index 31ee0a5..6063a7c 100644
--- a/pvmfw/avb/src/descriptor/common.rs
+++ b/pvmfw/avb/src/descriptor/common.rs
@@ -14,7 +14,8 @@
//! Structs and functions used by all the descriptors.
-use crate::utils::{self, is_not_null};
+use crate::utils::is_not_null;
+use avb::{IoError, IoResult};
use core::mem::MaybeUninit;
/// # Safety
@@ -24,14 +25,14 @@
pub(super) unsafe fn get_valid_descriptor<T>(
descriptor_ptr: *const T,
descriptor_validate_and_byteswap: unsafe extern "C" fn(src: *const T, dest: *mut T) -> bool,
-) -> utils::Result<T> {
+) -> IoResult<T> {
is_not_null(descriptor_ptr)?;
// SAFETY: It is safe because the caller ensures that `descriptor_ptr` is a non-null pointer
// pointing to a valid struct.
let descriptor = unsafe {
let mut desc = MaybeUninit::uninit();
if !descriptor_validate_and_byteswap(descriptor_ptr, desc.as_mut_ptr()) {
- return Err(avb::IoError::Io);
+ return Err(IoError::Io);
}
desc.assume_init()
};
diff --git a/pvmfw/avb/src/descriptor/hash.rs b/pvmfw/avb/src/descriptor/hash.rs
index 089268f..35db66d 100644
--- a/pvmfw/avb/src/descriptor/hash.rs
+++ b/pvmfw/avb/src/descriptor/hash.rs
@@ -16,7 +16,8 @@
use super::common::get_valid_descriptor;
use crate::partition::PartitionName;
-use crate::utils::{self, to_usize, usize_checked_add};
+use crate::utils::{to_usize, usize_checked_add};
+use avb::{IoError, IoResult};
use avb_bindgen::{
avb_hash_descriptor_validate_and_byteswap, AvbDescriptor, AvbHashDescriptor,
AVB_SHA256_DIGEST_SIZE,
@@ -47,19 +48,19 @@
pub(super) unsafe fn from_descriptor_ptr(
descriptor: *const AvbDescriptor,
data: &'a [u8],
- ) -> utils::Result<Self> {
+ ) -> IoResult<Self> {
// SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
// a valid `AvbDescriptor`.
let h = unsafe { HashDescriptorHeader::from_descriptor_ptr(descriptor)? };
let partition_name = data
.get(h.partition_name_range()?)
- .ok_or(avb::IoError::RangeOutsidePartition)?
+ .ok_or(IoError::RangeOutsidePartition)?
.try_into()?;
let digest = data
.get(h.digest_range()?)
- .ok_or(avb::IoError::RangeOutsidePartition)?
+ .ok_or(IoError::RangeOutsidePartition)?
.try_into()
- .map_err(|_| avb::IoError::InvalidValueSize)?;
+ .map_err(|_| IoError::InvalidValueSize)?;
Ok(Self { partition_name, digest })
}
}
@@ -71,7 +72,7 @@
///
/// Behavior is undefined if any of the following conditions are violated:
/// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
- unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> utils::Result<Self> {
+ unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> IoResult<Self> {
// SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
// a valid `AvbDescriptor`.
unsafe {
@@ -83,16 +84,16 @@
}
}
- fn partition_name_end(&self) -> utils::Result<usize> {
+ fn partition_name_end(&self) -> IoResult<usize> {
usize_checked_add(size_of::<AvbHashDescriptor>(), to_usize(self.0.partition_name_len)?)
}
- fn partition_name_range(&self) -> utils::Result<Range<usize>> {
+ fn partition_name_range(&self) -> IoResult<Range<usize>> {
let start = size_of::<AvbHashDescriptor>();
Ok(start..(self.partition_name_end()?))
}
- fn digest_range(&self) -> utils::Result<Range<usize>> {
+ fn digest_range(&self) -> IoResult<Range<usize>> {
let start = usize_checked_add(self.partition_name_end()?, to_usize(self.0.salt_len)?)?;
let end = usize_checked_add(start, to_usize(self.0.digest_len)?)?;
Ok(start..end)
diff --git a/pvmfw/avb/src/descriptor/property.rs b/pvmfw/avb/src/descriptor/property.rs
index 336623a..8145d64 100644
--- a/pvmfw/avb/src/descriptor/property.rs
+++ b/pvmfw/avb/src/descriptor/property.rs
@@ -15,7 +15,8 @@
//! Structs and functions relating to the property descriptor.
use super::common::get_valid_descriptor;
-use crate::utils::{self, to_usize, usize_checked_add};
+use crate::utils::{to_usize, usize_checked_add};
+use avb::{IoError, IoResult};
use avb_bindgen::{
avb_property_descriptor_validate_and_byteswap, AvbDescriptor, AvbPropertyDescriptor,
};
@@ -34,7 +35,7 @@
pub(super) unsafe fn from_descriptor_ptr(
descriptor: *const AvbDescriptor,
data: &'a [u8],
- ) -> utils::Result<Self> {
+ ) -> IoResult<Self> {
// SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
// a valid `AvbDescriptor`.
let h = unsafe { PropertyDescriptorHeader::from_descriptor_ptr(descriptor)? };
@@ -43,12 +44,12 @@
Ok(Self { key, value })
}
- fn get_valid_slice(data: &[u8], start: usize, end: usize) -> utils::Result<&[u8]> {
+ fn get_valid_slice(data: &[u8], start: usize, end: usize) -> IoResult<&[u8]> {
const NUL_BYTE: u8 = b'\0';
match data.get(end) {
- Some(&NUL_BYTE) => data.get(start..end).ok_or(avb::IoError::RangeOutsidePartition),
- _ => Err(avb::IoError::NoSuchValue),
+ Some(&NUL_BYTE) => data.get(start..end).ok_or(IoError::RangeOutsidePartition),
+ _ => Err(IoError::NoSuchValue),
}
}
}
@@ -60,7 +61,7 @@
///
/// Behavior is undefined if any of the following conditions are violated:
/// * The `descriptor` pointer must be non-null and point to a valid `AvbDescriptor`.
- unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> utils::Result<Self> {
+ unsafe fn from_descriptor_ptr(descriptor: *const AvbDescriptor) -> IoResult<Self> {
// SAFETY: It is safe as the raw pointer `descriptor` is non-null and points to
// a valid `AvbDescriptor`.
unsafe {
@@ -76,16 +77,16 @@
size_of::<AvbPropertyDescriptor>()
}
- fn key_end(&self) -> utils::Result<usize> {
+ fn key_end(&self) -> IoResult<usize> {
usize_checked_add(self.key_start(), to_usize(self.0.key_num_bytes)?)
}
- fn value_start(&self) -> utils::Result<usize> {
+ fn value_start(&self) -> IoResult<usize> {
// There is a NUL byte between key and value.
usize_checked_add(self.key_end()?, 1)
}
- fn value_end(&self) -> utils::Result<usize> {
+ fn value_end(&self) -> IoResult<usize> {
usize_checked_add(self.value_start()?, to_usize(self.0.value_num_bytes)?)
}
}
diff --git a/pvmfw/avb/src/error.rs b/pvmfw/avb/src/error.rs
index 0f052e8..4e3f27e 100644
--- a/pvmfw/avb/src/error.rs
+++ b/pvmfw/avb/src/error.rs
@@ -15,22 +15,23 @@
//! This module contains the error thrown by the payload verification API
//! and other errors used in the library.
+use avb::{IoError, SlotVerifyError};
use core::fmt;
-/// Wrapper around `avb::SlotVerifyError` to add custom pvmfw errors.
+/// Wrapper around `SlotVerifyError` to add custom pvmfw errors.
/// It is the error thrown by the payload verification API `verify_payload()`.
#[derive(Debug, PartialEq, Eq)]
pub enum PvmfwVerifyError {
- /// Passthrough `avb::SlotVerifyError` with no `SlotVerifyData`.
- AvbError(avb::SlotVerifyError<'static>),
+ /// Passthrough `SlotVerifyError` with no `SlotVerifyData`.
+ AvbError(SlotVerifyError<'static>),
/// VBMeta has invalid descriptors.
- InvalidDescriptors(avb::IoError),
+ InvalidDescriptors(IoError),
/// Unknown vbmeta property.
UnknownVbmetaProperty,
}
-impl From<avb::SlotVerifyError<'_>> for PvmfwVerifyError {
- fn from(error: avb::SlotVerifyError) -> Self {
+impl From<SlotVerifyError<'_>> for PvmfwVerifyError {
+ fn from(error: SlotVerifyError) -> Self {
// 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 aee93c8..9711f72 100644
--- a/pvmfw/avb/src/ops.rs
+++ b/pvmfw/avb/src/ops.rs
@@ -12,22 +12,14 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-//! Structs and functions relating to `AvbOps`.
+//! Structs and functions relating to AVB callback operations.
use crate::partition::PartitionName;
-use crate::utils::{self, as_ref, is_not_null, to_nonnull, write};
-use avb::internal::{result_to_io_enum, slot_verify_enum_to_result};
-use avb_bindgen::{
- avb_slot_verify, avb_slot_verify_data_free, AvbHashtreeErrorMode, AvbIOResult, AvbOps,
- AvbPartitionData, AvbSlotVerifyData, AvbSlotVerifyFlags, AvbVBMetaData,
+use avb::{
+ slot_verify, HashtreeErrorMode, IoError, IoResult, PublicKeyForPartitionInfo, SlotVerifyData,
+ SlotVerifyFlags, SlotVerifyResult,
};
-use core::{
- ffi::{c_char, c_void, CStr},
- mem::MaybeUninit,
- ptr, slice,
-};
-
-const NULL_BYTE: &[u8] = b"\0";
+use core::ffi::CStr;
pub(crate) struct Payload<'a> {
kernel: &'a [u8],
@@ -35,15 +27,6 @@
trusted_public_key: &'a [u8],
}
-impl<'a> AsRef<Payload<'a>> for AvbOps {
- fn as_ref(&self) -> &Payload<'a> {
- let payload = self.user_data as *const Payload;
- // SAFETY: It is safe to cast the `AvbOps.user_data` to Payload as we have saved a
- // pointer to a valid value of Payload in user_data when creating AvbOps.
- unsafe { &*payload }
- }
-}
-
impl<'a> Payload<'a> {
pub(crate) fn new(
kernel: &'a [u8],
@@ -53,148 +36,116 @@
Self { kernel, initrd, trusted_public_key }
}
- fn get_partition(&self, partition_name: *const c_char) -> Result<&[u8], avb::IoError> {
+ fn get_partition(&self, partition_name: &CStr) -> IoResult<&[u8]> {
match partition_name.try_into()? {
PartitionName::Kernel => Ok(self.kernel),
PartitionName::InitrdNormal | PartitionName::InitrdDebug => {
- self.initrd.ok_or(avb::IoError::NoSuchPartition)
+ self.initrd.ok_or(IoError::NoSuchPartition)
}
}
}
}
-/// `Ops` wraps the class `AvbOps` in libavb. It provides pvmfw customized
-/// operations used in the verification.
-pub(crate) struct Ops(AvbOps);
-
-impl<'a> From<&mut Payload<'a>> for Ops {
- fn from(payload: &mut Payload<'a>) -> Self {
- let avb_ops = AvbOps {
- user_data: payload as *mut _ as *mut c_void,
- ab_ops: ptr::null_mut(),
- atx_ops: ptr::null_mut(),
- read_from_partition: Some(read_from_partition),
- get_preloaded_partition: Some(get_preloaded_partition),
- write_to_partition: None,
- validate_vbmeta_public_key: Some(validate_vbmeta_public_key),
- read_rollback_index: Some(read_rollback_index),
- write_rollback_index: None,
- read_is_device_unlocked: Some(read_is_device_unlocked),
- get_unique_guid_for_partition: Some(get_unique_guid_for_partition),
- get_size_of_partition: Some(get_size_of_partition),
- read_persistent_value: None,
- write_persistent_value: None,
- validate_public_key_for_partition: None,
- };
- Self(avb_ops)
- }
+/// Pvmfw customized operations used in the verification.
+pub(crate) struct Ops<'a> {
+ payload: &'a Payload<'a>,
}
-impl Ops {
+impl<'a> Ops<'a> {
+ pub(crate) fn new(payload: &'a Payload<'a>) -> Self {
+ Self { payload }
+ }
+
pub(crate) fn verify_partition(
&mut self,
partition_name: &CStr,
- ) -> 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();
- // SAFETY: It is safe to call `avb_slot_verify()` as the pointer arguments (`ops`,
- // `requested_partitions` and `ab_suffix`) passed to the method are all valid and
- // initialized.
- let result = unsafe {
- avb_slot_verify(
- &mut self.0,
- requested_partitions.as_ptr(),
- ab_suffix.as_ptr(),
- AvbSlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
- AvbHashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
- out_data.as_mut_ptr(),
- )
- };
- slot_verify_enum_to_result(result)?;
- // SAFETY: This is safe because `out_data` has been properly initialized after
- // calling `avb_slot_verify` and it returns OK.
- let out_data = unsafe { out_data.assume_init() };
- out_data.try_into()
+ ) -> SlotVerifyResult<SlotVerifyData> {
+ slot_verify(
+ self,
+ &[partition_name],
+ None, // No partition slot suffix.
+ SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
+ HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE,
+ )
}
}
-extern "C" fn read_is_device_unlocked(
- _ops: *mut AvbOps,
- out_is_unlocked: *mut bool,
-) -> AvbIOResult {
- result_to_io_enum(write(out_is_unlocked, false))
+impl<'a> avb::Ops for Ops<'a> {
+ fn read_from_partition(
+ &mut self,
+ partition: &CStr,
+ offset: i64,
+ buffer: &mut [u8],
+ ) -> IoResult<usize> {
+ let partition = self.payload.get_partition(partition)?;
+ copy_data_to_dst(partition, offset, buffer)?;
+ Ok(buffer.len())
+ }
+
+ fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&[u8]> {
+ self.payload.get_partition(partition)
+ }
+
+ fn validate_vbmeta_public_key(
+ &mut self,
+ public_key: &[u8],
+ _public_key_metadata: Option<&[u8]>,
+ ) -> IoResult<bool> {
+ // The public key metadata is not used when we build the VBMeta.
+ Ok(self.payload.trusted_public_key == public_key)
+ }
+
+ fn read_rollback_index(&mut self, _rollback_index_location: usize) -> IoResult<u64> {
+ // TODO(291213394) : Refine this comment once capability for rollback protection is defined.
+ // pvmfw does not compare stored_rollback_index with rollback_index for Antirollback
+ // protection. Hence, we set `out_rollback_index` to 0 to ensure that the rollback_index
+ // (including default: 0) is never smaller than it, thus the rollback index check will pass.
+ Ok(0)
+ }
+
+ fn write_rollback_index(
+ &mut self,
+ _rollback_index_location: usize,
+ _index: u64,
+ ) -> IoResult<()> {
+ Err(IoError::NotImplemented)
+ }
+
+ fn read_is_device_unlocked(&mut self) -> IoResult<bool> {
+ Ok(false)
+ }
+
+ fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> {
+ let partition = self.payload.get_partition(partition)?;
+ u64::try_from(partition.len()).map_err(|_| IoError::InvalidValueSize)
+ }
+
+ fn read_persistent_value(&mut self, _name: &CStr, _value: &mut [u8]) -> IoResult<usize> {
+ Err(IoError::NotImplemented)
+ }
+
+ fn write_persistent_value(&mut self, _name: &CStr, _value: &[u8]) -> IoResult<()> {
+ Err(IoError::NotImplemented)
+ }
+
+ fn erase_persistent_value(&mut self, _name: &CStr) -> IoResult<()> {
+ Err(IoError::NotImplemented)
+ }
+
+ fn validate_public_key_for_partition(
+ &mut self,
+ _partition: &CStr,
+ _public_key: &[u8],
+ _public_key_metadata: Option<&[u8]>,
+ ) -> IoResult<PublicKeyForPartitionInfo> {
+ Err(IoError::NotImplemented)
+ }
}
-extern "C" fn get_preloaded_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
- num_bytes: usize,
- out_pointer: *mut *mut u8,
- out_num_bytes_preloaded: *mut usize,
-) -> AvbIOResult {
- result_to_io_enum(try_get_preloaded_partition(
- ops,
- partition,
- num_bytes,
- out_pointer,
- out_num_bytes_preloaded,
- ))
-}
-
-fn try_get_preloaded_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
- num_bytes: usize,
- out_pointer: *mut *mut u8,
- out_num_bytes_preloaded: *mut usize,
-) -> utils::Result<()> {
- let ops = as_ref(ops)?;
- let partition = ops.as_ref().get_partition(partition)?;
- write(out_pointer, partition.as_ptr() as *mut u8)?;
- write(out_num_bytes_preloaded, partition.len().min(num_bytes))
-}
-
-extern "C" fn read_from_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
- offset: i64,
- num_bytes: usize,
- buffer: *mut c_void,
- out_num_read: *mut usize,
-) -> AvbIOResult {
- result_to_io_enum(try_read_from_partition(
- ops,
- partition,
- offset,
- num_bytes,
- buffer,
- out_num_read,
- ))
-}
-
-fn try_read_from_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
- offset: i64,
- num_bytes: usize,
- buffer: *mut c_void,
- out_num_read: *mut usize,
-) -> utils::Result<()> {
- let ops = as_ref(ops)?;
- let partition = ops.as_ref().get_partition(partition)?;
- let buffer = to_nonnull(buffer)?;
- // SAFETY: It is safe to copy the requested number of bytes to `buffer` as `buffer`
- // is created to point to the `num_bytes` of bytes in memory.
- let buffer_slice = unsafe { slice::from_raw_parts_mut(buffer.as_ptr() as *mut u8, num_bytes) };
- copy_data_to_dst(partition, offset, buffer_slice)?;
- write(out_num_read, buffer_slice.len())
-}
-
-fn copy_data_to_dst(src: &[u8], offset: i64, dst: &mut [u8]) -> utils::Result<()> {
- let start = to_copy_start(offset, src.len()).ok_or(avb::IoError::InvalidValueSize)?;
- let end = start.checked_add(dst.len()).ok_or(avb::IoError::InvalidValueSize)?;
- dst.copy_from_slice(src.get(start..end).ok_or(avb::IoError::RangeOutsidePartition)?);
+fn copy_data_to_dst(src: &[u8], offset: i64, dst: &mut [u8]) -> IoResult<()> {
+ let start = to_copy_start(offset, src.len()).ok_or(IoError::InvalidValueSize)?;
+ let end = start.checked_add(dst.len()).ok_or(IoError::InvalidValueSize)?;
+ dst.copy_from_slice(src.get(start..end).ok_or(IoError::RangeOutsidePartition)?);
Ok(())
}
@@ -203,143 +154,3 @@
.ok()
.or_else(|| isize::try_from(offset).ok().and_then(|v| len.checked_add_signed(v)))
}
-
-extern "C" fn get_size_of_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
- out_size_num_bytes: *mut u64,
-) -> AvbIOResult {
- result_to_io_enum(try_get_size_of_partition(ops, partition, out_size_num_bytes))
-}
-
-fn try_get_size_of_partition(
- ops: *mut AvbOps,
- partition: *const c_char,
- out_size_num_bytes: *mut u64,
-) -> utils::Result<()> {
- let ops = as_ref(ops)?;
- let partition = ops.as_ref().get_partition(partition)?;
- let partition_size =
- u64::try_from(partition.len()).map_err(|_| avb::IoError::InvalidValueSize)?;
- write(out_size_num_bytes, partition_size)
-}
-
-extern "C" fn read_rollback_index(
- _ops: *mut AvbOps,
- _rollback_index_location: usize,
- out_rollback_index: *mut u64,
-) -> AvbIOResult {
- // This method is used by `avb_slot_verify()` to read the stored_rollback_index at
- // rollback_index_location.
-
- // TODO(291213394) : Refine this comment once capability for rollback protection is defined.
- // pvmfw does not compare stored_rollback_index with rollback_index for Antirollback protection
- // Hence, we set `out_rollback_index` to 0 to ensure that the
- // rollback_index (including default: 0) is never smaller than it,
- // thus the rollback index check will pass.
- result_to_io_enum(write(out_rollback_index, 0))
-}
-
-extern "C" fn get_unique_guid_for_partition(
- _ops: *mut AvbOps,
- _partition: *const c_char,
- _guid_buf: *mut c_char,
- _guid_buf_size: usize,
-) -> AvbIOResult {
- // TODO(b/256148034): Check if it's possible to throw an error here instead of having
- // an empty method.
- // This method is required by `avb_slot_verify()`.
- AvbIOResult::AVB_IO_RESULT_OK
-}
-
-extern "C" fn validate_vbmeta_public_key(
- ops: *mut AvbOps,
- public_key_data: *const u8,
- public_key_length: usize,
- public_key_metadata: *const u8,
- public_key_metadata_length: usize,
- out_is_trusted: *mut bool,
-) -> AvbIOResult {
- result_to_io_enum(try_validate_vbmeta_public_key(
- ops,
- public_key_data,
- public_key_length,
- public_key_metadata,
- public_key_metadata_length,
- out_is_trusted,
- ))
-}
-
-fn try_validate_vbmeta_public_key(
- ops: *mut AvbOps,
- public_key_data: *const u8,
- public_key_length: usize,
- _public_key_metadata: *const u8,
- _public_key_metadata_length: usize,
- out_is_trusted: *mut bool,
-) -> utils::Result<()> {
- // The public key metadata is not used when we build the VBMeta.
- is_not_null(public_key_data)?;
- // SAFETY: It is safe to create a slice with the given pointer and length as
- // `public_key_data` is a valid pointer and it points to an array of length
- // `public_key_length`.
- let public_key = unsafe { slice::from_raw_parts(public_key_data, public_key_length) };
- let ops = as_ref(ops)?;
- let trusted_public_key = ops.as_ref().trusted_public_key;
- write(out_is_trusted, public_key == trusted_public_key)
-}
-
-pub(crate) struct AvbSlotVerifyDataWrap(*mut AvbSlotVerifyData);
-
-impl TryFrom<*mut AvbSlotVerifyData> for AvbSlotVerifyDataWrap {
- type Error = avb::SlotVerifyError<'static>;
-
- fn try_from(data: *mut AvbSlotVerifyData) -> Result<Self, Self::Error> {
- is_not_null(data).map_err(|_| avb::SlotVerifyError::Io)?;
- Ok(Self(data))
- }
-}
-
-impl Drop for AvbSlotVerifyDataWrap {
- fn drop(&mut self) {
- // SAFETY: This is safe because `self.0` is checked nonnull when the
- // instance is created. We can free this pointer when the instance is
- // no longer needed.
- unsafe {
- avb_slot_verify_data_free(self.0);
- }
- }
-}
-
-impl AsRef<AvbSlotVerifyData> for AvbSlotVerifyDataWrap {
- fn as_ref(&self) -> &AvbSlotVerifyData {
- // This is safe because `self.0` is checked nonnull when the instance is created.
- as_ref(self.0).unwrap()
- }
-}
-
-impl AvbSlotVerifyDataWrap {
- pub(crate) fn vbmeta_images(&self) -> Result<&[AvbVBMetaData], avb::SlotVerifyError> {
- let data = self.as_ref();
- is_not_null(data.vbmeta_images).map_err(|_| avb::SlotVerifyError::Io)?;
- let vbmeta_images =
- // SAFETY: It is safe as the raw pointer `data.vbmeta_images` is a nonnull pointer.
- unsafe { slice::from_raw_parts(data.vbmeta_images, data.num_vbmeta_images) };
- Ok(vbmeta_images)
- }
-
- pub(crate) fn loaded_partitions(&self) -> Result<&[AvbPartitionData], avb::SlotVerifyError> {
- let data = self.as_ref();
- is_not_null(data.loaded_partitions).map_err(|_| avb::SlotVerifyError::Io)?;
- let loaded_partitions =
- // SAFETY: It is safe as the raw pointer `data.loaded_partitions` is a nonnull pointer and
- // is guaranteed by libavb to point to a valid `AvbPartitionData` array as part of the
- // `AvbSlotVerifyData` struct.
- unsafe { slice::from_raw_parts(data.loaded_partitions, data.num_loaded_partitions) };
- Ok(loaded_partitions)
- }
-
- pub(crate) fn rollback_indexes(&self) -> &[u64] {
- &self.as_ref().rollback_indexes
- }
-}
diff --git a/pvmfw/avb/src/partition.rs b/pvmfw/avb/src/partition.rs
index ca450c9..3fe9479 100644
--- a/pvmfw/avb/src/partition.rs
+++ b/pvmfw/avb/src/partition.rs
@@ -14,8 +14,8 @@
//! Struct and functions relating to well-known partition names.
-use crate::utils::is_not_null;
-use core::ffi::{c_char, CStr};
+use avb::IoError;
+use core::ffi::CStr;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) enum PartitionName {
@@ -51,39 +51,28 @@
}
}
-impl TryFrom<*const c_char> for PartitionName {
- type Error = avb::IoError;
-
- fn try_from(partition_name: *const c_char) -> Result<Self, Self::Error> {
- is_not_null(partition_name)?;
- // SAFETY: It is safe as the raw pointer `partition_name` is a nonnull pointer.
- let partition_name = unsafe { CStr::from_ptr(partition_name) };
- partition_name.try_into()
- }
-}
-
impl TryFrom<&CStr> for PartitionName {
- type Error = avb::IoError;
+ type Error = IoError;
fn try_from(partition_name: &CStr) -> Result<Self, Self::Error> {
match partition_name.to_bytes_with_nul() {
Self::KERNEL_PARTITION_NAME => Ok(Self::Kernel),
Self::INITRD_NORMAL_PARTITION_NAME => Ok(Self::InitrdNormal),
Self::INITRD_DEBUG_PARTITION_NAME => Ok(Self::InitrdDebug),
- _ => Err(avb::IoError::NoSuchPartition),
+ _ => Err(IoError::NoSuchPartition),
}
}
}
impl TryFrom<&[u8]> for PartitionName {
- type Error = avb::IoError;
+ type Error = IoError;
fn try_from(non_null_terminated_name: &[u8]) -> Result<Self, Self::Error> {
match non_null_terminated_name {
x if x == Self::Kernel.as_non_null_terminated_bytes() => Ok(Self::Kernel),
x if x == Self::InitrdNormal.as_non_null_terminated_bytes() => Ok(Self::InitrdNormal),
x if x == Self::InitrdDebug.as_non_null_terminated_bytes() => Ok(Self::InitrdDebug),
- _ => Err(avb::IoError::NoSuchPartition),
+ _ => Err(IoError::NoSuchPartition),
}
}
}
diff --git a/pvmfw/avb/src/utils.rs b/pvmfw/avb/src/utils.rs
index f4f15e1..b4f099b 100644
--- a/pvmfw/avb/src/utils.rs
+++ b/pvmfw/avb/src/utils.rs
@@ -14,42 +14,20 @@
//! Common utility functions.
-use core::ptr::NonNull;
-use core::result;
+use avb::{IoError, IoResult};
-pub(crate) type Result<T> = result::Result<T, avb::IoError>;
-
-pub(crate) fn write<T>(ptr: *mut T, value: T) -> Result<()> {
- let ptr = to_nonnull(ptr)?;
- // SAFETY: It is safe as the raw pointer `ptr` is a non-null pointer.
- unsafe {
- *ptr.as_ptr() = value;
- }
- Ok(())
-}
-
-pub(crate) fn as_ref<'a, T>(ptr: *mut T) -> Result<&'a T> {
- let ptr = to_nonnull(ptr)?;
- // SAFETY: It is safe as the raw pointer `ptr` is a non-null pointer.
- unsafe { Ok(ptr.as_ref()) }
-}
-
-pub(crate) fn to_nonnull<T>(ptr: *mut T) -> Result<NonNull<T>> {
- NonNull::new(ptr).ok_or(avb::IoError::NoSuchValue)
-}
-
-pub(crate) fn is_not_null<T>(ptr: *const T) -> Result<()> {
+pub(crate) fn is_not_null<T>(ptr: *const T) -> IoResult<()> {
if ptr.is_null() {
- Err(avb::IoError::NoSuchValue)
+ Err(IoError::NoSuchValue)
} else {
Ok(())
}
}
-pub(crate) fn to_usize<T: TryInto<usize>>(num: T) -> Result<usize> {
- num.try_into().map_err(|_| avb::IoError::InvalidValueSize)
+pub(crate) fn to_usize<T: TryInto<usize>>(num: T) -> IoResult<usize> {
+ num.try_into().map_err(|_| IoError::InvalidValueSize)
}
-pub(crate) fn usize_checked_add(x: usize, y: usize) -> Result<usize> {
- x.checked_add(y).ok_or(avb::IoError::InvalidValueSize)
+pub(crate) fn usize_checked_add(x: usize, y: usize) -> IoResult<usize> {
+ x.checked_add(y).ok_or(IoError::InvalidValueSize)
}
diff --git a/pvmfw/avb/src/verify.rs b/pvmfw/avb/src/verify.rs
index 3274033..ac015e0 100644
--- a/pvmfw/avb/src/verify.rs
+++ b/pvmfw/avb/src/verify.rs
@@ -20,10 +20,9 @@
use crate::PvmfwVerifyError;
use alloc::vec;
use alloc::vec::Vec;
-use avb_bindgen::{AvbPartitionData, AvbVBMetaData};
-use core::ffi::c_char;
+use avb::{PartitionData, SlotVerifyError, SlotVerifyNoDataResult, VbmetaData};
-// We use this for the rollback_index field if AvbSlotVerifyDataWrap has empty rollback_indexes
+// We use this for the rollback_index field if SlotVerifyData has empty rollback_indexes
const DEFAULT_ROLLBACK_INDEX: u64 = 0;
/// Verified data returned when the payload verification succeeds.
@@ -84,7 +83,7 @@
_ => return Err(PvmfwVerifyError::UnknownVbmetaProperty),
};
if res.contains(&cap) {
- return Err(avb::SlotVerifyError::InvalidMetadata.into());
+ return Err(SlotVerifyError::InvalidMetadata.into());
}
res.push(cap);
}
@@ -92,55 +91,51 @@
}
}
-fn verify_only_one_vbmeta_exists(
- vbmeta_images: &[AvbVBMetaData],
-) -> Result<(), avb::SlotVerifyError<'static>> {
- if vbmeta_images.len() == 1 {
+fn verify_only_one_vbmeta_exists(vbmeta_data: &[VbmetaData]) -> SlotVerifyNoDataResult<()> {
+ if vbmeta_data.len() == 1 {
Ok(())
} else {
- Err(avb::SlotVerifyError::InvalidMetadata)
+ Err(SlotVerifyError::InvalidMetadata)
}
}
-fn verify_vbmeta_is_from_kernel_partition(
- vbmeta_image: &AvbVBMetaData,
-) -> Result<(), avb::SlotVerifyError<'static>> {
- match (vbmeta_image.partition_name as *const c_char).try_into() {
+fn verify_vbmeta_is_from_kernel_partition(vbmeta_image: &VbmetaData) -> SlotVerifyNoDataResult<()> {
+ match vbmeta_image.partition_name().try_into() {
Ok(PartitionName::Kernel) => Ok(()),
- _ => Err(avb::SlotVerifyError::InvalidMetadata),
+ _ => Err(SlotVerifyError::InvalidMetadata),
}
}
fn verify_vbmeta_has_only_one_hash_descriptor(
descriptors: &Descriptors,
-) -> Result<(), avb::SlotVerifyError<'static>> {
+) -> SlotVerifyNoDataResult<()> {
if descriptors.num_hash_descriptor() == 1 {
Ok(())
} else {
- Err(avb::SlotVerifyError::InvalidMetadata)
+ Err(SlotVerifyError::InvalidMetadata)
}
}
fn verify_loaded_partition_has_expected_length(
- loaded_partitions: &[AvbPartitionData],
+ loaded_partitions: &[PartitionData],
partition_name: PartitionName,
expected_len: usize,
-) -> Result<(), avb::SlotVerifyError<'static>> {
+) -> SlotVerifyNoDataResult<()> {
if loaded_partitions.len() != 1 {
// Only one partition should be loaded in each verify result.
- return Err(avb::SlotVerifyError::Io);
+ return Err(SlotVerifyError::Io);
}
- let loaded_partition = loaded_partitions[0];
- if !PartitionName::try_from(loaded_partition.partition_name as *const c_char)
+ let loaded_partition = &loaded_partitions[0];
+ if !PartitionName::try_from(loaded_partition.partition_name())
.map_or(false, |p| p == partition_name)
{
// Only the requested partition should be loaded.
- return Err(avb::SlotVerifyError::Io);
+ return Err(SlotVerifyError::Io);
}
- if loaded_partition.data_size == expected_len {
+ if loaded_partition.data().len() == expected_len {
Ok(())
} else {
- Err(avb::SlotVerifyError::Verification(None))
+ Err(SlotVerifyError::Verification(None))
}
}
@@ -158,28 +153,40 @@
.and_then(Capability::get_capabilities)
}
+/// Verifies the given initrd partition, and checks that the resulting contents looks like expected.
+fn verify_initrd(
+ ops: &mut Ops,
+ partition_name: PartitionName,
+ expected_initrd: &[u8],
+) -> SlotVerifyNoDataResult<()> {
+ let result =
+ ops.verify_partition(partition_name.as_cstr()).map_err(|e| e.without_verify_data())?;
+ verify_loaded_partition_has_expected_length(
+ result.partition_data(),
+ partition_name,
+ expected_initrd.len(),
+ )
+}
+
/// Verifies the payload (signed kernel + initrd) against the trusted public key.
pub fn verify_payload<'a>(
kernel: &[u8],
initrd: Option<&[u8]>,
trusted_public_key: &'a [u8],
) -> Result<VerifiedBootData<'a>, PvmfwVerifyError> {
- let mut payload = Payload::new(kernel, initrd, trusted_public_key);
- let mut ops = Ops::from(&mut payload);
+ let payload = Payload::new(kernel, initrd, trusted_public_key);
+ let mut ops = Ops::new(&payload);
let kernel_verify_result = ops.verify_partition(PartitionName::Kernel.as_cstr())?;
- let vbmeta_images = kernel_verify_result.vbmeta_images()?;
+ let vbmeta_images = kernel_verify_result.vbmeta_data();
// TODO(b/302093437): Use explicit rollback_index_location instead of default
// location (first element).
let rollback_index =
*kernel_verify_result.rollback_indexes().first().unwrap_or(&DEFAULT_ROLLBACK_INDEX);
verify_only_one_vbmeta_exists(vbmeta_images)?;
- let vbmeta_image = vbmeta_images[0];
- verify_vbmeta_is_from_kernel_partition(&vbmeta_image)?;
- // SAFETY: It is safe because the `vbmeta_image` is collected from `AvbSlotVerifyData`,
- // which is returned by `avb_slot_verify()` when the verification succeeds. It is
- // guaranteed by libavb to be non-null and to point to a valid VBMeta structure.
- let descriptors = unsafe { Descriptors::from_vbmeta(vbmeta_image)? };
+ let vbmeta_image = &vbmeta_images[0];
+ verify_vbmeta_is_from_kernel_partition(vbmeta_image)?;
+ let descriptors = Descriptors::from_vbmeta(vbmeta_image)?;
let capabilities = verify_property_and_get_capabilities(&descriptors)?;
let kernel_descriptor = descriptors.find_hash_descriptor(PartitionName::Kernel)?;
@@ -196,20 +203,15 @@
}
let initrd = initrd.unwrap();
- let (debug_level, initrd_verify_result, initrd_partition_name) =
- if let Ok(result) = ops.verify_partition(PartitionName::InitrdNormal.as_cstr()) {
- (DebugLevel::None, result, PartitionName::InitrdNormal)
- } else if let Ok(result) = ops.verify_partition(PartitionName::InitrdDebug.as_cstr()) {
- (DebugLevel::Full, result, PartitionName::InitrdDebug)
+ let mut initrd_ops = Ops::new(&payload);
+ let (debug_level, initrd_partition_name) =
+ if verify_initrd(&mut initrd_ops, PartitionName::InitrdNormal, initrd).is_ok() {
+ (DebugLevel::None, PartitionName::InitrdNormal)
+ } else if verify_initrd(&mut initrd_ops, PartitionName::InitrdDebug, initrd).is_ok() {
+ (DebugLevel::Full, PartitionName::InitrdDebug)
} else {
- return Err(avb::SlotVerifyError::Verification(None).into());
+ return Err(SlotVerifyError::Verification(None).into());
};
- let loaded_partitions = initrd_verify_result.loaded_partitions()?;
- verify_loaded_partition_has_expected_length(
- loaded_partitions,
- initrd_partition_name,
- initrd.len(),
- )?;
let initrd_descriptor = descriptors.find_hash_descriptor(initrd_partition_name)?;
Ok(VerifiedBootData {
debug_level,
diff --git a/pvmfw/avb/tests/api_test.rs b/pvmfw/avb/tests/api_test.rs
index 84f83c2..6dc5a0a 100644
--- a/pvmfw/avb/tests/api_test.rs
+++ b/pvmfw/avb/tests/api_test.rs
@@ -17,6 +17,7 @@
mod utils;
use anyhow::{anyhow, Result};
+use avb::{IoError, SlotVerifyError};
use avb_bindgen::{AvbFooter, AvbVBMetaImageHeader};
use pvmfw_avb::{verify_payload, Capability, DebugLevel, PvmfwVerifyError, VerifiedBootData};
use std::{fs, mem::size_of, ptr};
@@ -87,7 +88,7 @@
&fs::read(TEST_IMG_WITH_NON_INITRD_HASHDESC_PATH)?,
/* initrd= */ None,
&load_trusted_public_key()?,
- PvmfwVerifyError::InvalidDescriptors(avb::IoError::NoSuchPartition),
+ PvmfwVerifyError::InvalidDescriptors(IoError::NoSuchPartition),
)
}
@@ -97,7 +98,7 @@
&fs::read(TEST_IMG_WITH_INITRD_AND_NON_INITRD_DESC_PATH)?,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- PvmfwVerifyError::InvalidDescriptors(avb::IoError::NoSuchPartition),
+ PvmfwVerifyError::InvalidDescriptors(IoError::NoSuchPartition),
)
}
@@ -141,7 +142,7 @@
&fs::read(TEST_IMG_WITH_MULTIPLE_PROPS_PATH)?,
/* initrd= */ None,
&load_trusted_public_key()?,
- PvmfwVerifyError::InvalidDescriptors(avb::IoError::Io),
+ PvmfwVerifyError::InvalidDescriptors(IoError::Io),
)
}
@@ -151,7 +152,7 @@
&fs::read(TEST_IMG_WITH_DUPLICATED_CAP_PATH)?,
/* initrd= */ None,
&load_trusted_public_key()?,
- avb::SlotVerifyError::InvalidMetadata.into(),
+ SlotVerifyError::InvalidMetadata.into(),
)
}
@@ -171,7 +172,7 @@
&load_latest_signed_kernel()?,
/* initrd= */ None,
&load_trusted_public_key()?,
- avb::SlotVerifyError::InvalidMetadata.into(),
+ SlotVerifyError::InvalidMetadata.into(),
)
}
@@ -181,7 +182,7 @@
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
/* trusted_public_key= */ &[0u8; 0],
- avb::SlotVerifyError::PublicKeyRejected.into(),
+ SlotVerifyError::PublicKeyRejected.into(),
)
}
@@ -191,7 +192,7 @@
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
/* trusted_public_key= */ &[0u8; 512],
- avb::SlotVerifyError::PublicKeyRejected.into(),
+ SlotVerifyError::PublicKeyRejected.into(),
)
}
@@ -201,7 +202,7 @@
&load_latest_signed_kernel()?,
&load_latest_initrd_normal()?,
&fs::read(PUBLIC_KEY_RSA2048_PATH)?,
- avb::SlotVerifyError::PublicKeyRejected.into(),
+ SlotVerifyError::PublicKeyRejected.into(),
)
}
@@ -211,7 +212,7 @@
&load_latest_signed_kernel()?,
/* initrd= */ &fs::read(UNSIGNED_TEST_IMG_PATH)?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification(None).into(),
+ SlotVerifyError::Verification(None).into(),
)
}
@@ -221,7 +222,7 @@
&fs::read(UNSIGNED_TEST_IMG_PATH)?,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Io.into(),
+ SlotVerifyError::Io.into(),
)
}
@@ -234,7 +235,7 @@
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification(None).into(),
+ SlotVerifyError::Verification(None).into(),
)
}
@@ -272,7 +273,7 @@
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Io.into(),
+ SlotVerifyError::Io.into(),
)?;
}
Ok(())
@@ -288,7 +289,7 @@
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::InvalidMetadata.into(),
+ SlotVerifyError::InvalidMetadata.into(),
)
}
@@ -301,7 +302,7 @@
&load_latest_signed_kernel()?,
&initrd,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification(None).into(),
+ SlotVerifyError::Verification(None).into(),
)
}
@@ -317,7 +318,7 @@
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::InvalidMetadata.into(),
+ SlotVerifyError::InvalidMetadata.into(),
)
}
@@ -340,13 +341,13 @@
&kernel,
&load_latest_initrd_normal()?,
&empty_public_key,
- avb::SlotVerifyError::Verification(None).into(),
+ SlotVerifyError::Verification(None).into(),
)?;
assert_payload_verification_with_initrd_fails(
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification(None).into(),
+ SlotVerifyError::Verification(None).into(),
)
}
@@ -384,7 +385,7 @@
&kernel,
&load_latest_initrd_normal()?,
&load_trusted_public_key()?,
- avb::SlotVerifyError::Verification(None).into(),
+ SlotVerifyError::Verification(None).into(),
)
}
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
index 112c24c..99bf589 100644
--- a/pvmfw/src/dice.rs
+++ b/pvmfw/src/dice.rs
@@ -14,16 +14,13 @@
//! Support for DICE derivation and BCC generation.
-use core::ffi::c_void;
use core::mem::size_of;
-use core::slice;
use cstr::cstr;
use diced_open_dice::{
bcc_format_config_descriptor, bcc_handover_main_flow, hash, Config, DiceConfigValues, DiceMode,
Hash, InputValues, HIDDEN_SIZE,
};
-use pvmfw_avb::{DebugLevel, Digest, VerifiedBootData};
-use vmbase::memory::flushed_zeroize;
+use pvmfw_avb::{Capability, DebugLevel, Digest, VerifiedBootData};
fn to_dice_mode(debug_level: DebugLevel) -> DiceMode {
match debug_level {
@@ -46,6 +43,7 @@
pub auth_hash: Hash,
pub mode: DiceMode,
pub security_version: u64,
+ pub rkp_vm_marker: bool,
}
impl PartialInputs {
@@ -55,8 +53,9 @@
let mode = to_dice_mode(data.debug_level);
// We use rollback_index from vbmeta as the security_version field in dice certificate.
let security_version = data.rollback_index;
+ let rkp_vm_marker = data.has_capability(Capability::RemoteAttest);
- Ok(Self { code_hash, auth_hash, mode, security_version })
+ Ok(Self { code_hash, auth_hash, mode, security_version, rkp_vm_marker })
}
pub fn write_next_bcc(
@@ -66,15 +65,7 @@
next_bcc: &mut [u8],
) -> diced_open_dice::Result<()> {
let mut config_descriptor_buffer = [0; 128];
- let config_values = DiceConfigValues {
- component_name: Some(cstr!("vm_entry")),
- security_version: if cfg!(llpvm_changes) { Some(self.security_version) } else { None },
- ..Default::default()
- };
-
- let config_descriptor_size =
- bcc_format_config_descriptor(&config_values, &mut config_descriptor_buffer)?;
- let config = &config_descriptor_buffer[..config_descriptor_size];
+ let config = self.generate_config_descriptor(&mut config_descriptor_buffer)?;
let dice_inputs = InputValues::new(
self.code_hash,
@@ -86,17 +77,138 @@
let _ = bcc_handover_main_flow(current_bcc_handover, &dice_inputs, next_bcc)?;
Ok(())
}
+
+ fn generate_config_descriptor<'a>(
+ &self,
+ config_descriptor_buffer: &'a mut [u8],
+ ) -> diced_open_dice::Result<&'a [u8]> {
+ let config_values = DiceConfigValues {
+ component_name: Some(cstr!("vm_entry")),
+ security_version: if cfg!(dice_changes) { Some(self.security_version) } else { None },
+ rkp_vm_marker: self.rkp_vm_marker,
+ ..Default::default()
+ };
+ let config_descriptor_size =
+ bcc_format_config_descriptor(&config_values, config_descriptor_buffer)?;
+ let config = &config_descriptor_buffer[..config_descriptor_size];
+ Ok(config)
+ }
}
/// Flushes data caches over the provided address range.
///
/// # Safety
///
-/// The provided address and size must be to a valid address range (typically on the stack, .bss,
-/// .data, or provided BCC).
+/// The provided address and size must be to an address range that is valid for read and write
+/// (typically on the stack, .bss, .data, or provided BCC) from a single allocation
+/// (e.g. stack array).
#[no_mangle]
-unsafe extern "C" fn DiceClearMemory(_ctx: *mut c_void, size: usize, addr: *mut c_void) {
- // SAFETY: We must trust that the slice will be valid arrays/variables on the C code stack.
+#[cfg(not(test))]
+unsafe extern "C" fn DiceClearMemory(
+ _ctx: *mut core::ffi::c_void,
+ size: usize,
+ addr: *mut core::ffi::c_void,
+) {
+ use core::slice;
+ use vmbase::memory::flushed_zeroize;
+
+ // SAFETY: We require our caller to provide a valid range within a single object. The open-dice
+ // always calls this on individual stack-allocated arrays which ensures that.
let region = unsafe { slice::from_raw_parts_mut(addr as *mut u8, size) };
flushed_zeroize(region)
}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+ use ciborium::Value;
+ use std::collections::HashMap;
+ use std::vec;
+
+ const COMPONENT_NAME_KEY: i64 = -70002;
+ const COMPONENT_VERSION_KEY: i64 = -70003;
+ const RESETTABLE_KEY: i64 = -70004;
+ const SECURITY_VERSION_KEY: i64 = -70005;
+ const RKP_VM_MARKER_KEY: i64 = -70006;
+
+ const BASE_VB_DATA: VerifiedBootData = VerifiedBootData {
+ debug_level: DebugLevel::None,
+ kernel_digest: [1u8; size_of::<Digest>()],
+ initrd_digest: Some([2u8; size_of::<Digest>()]),
+ public_key: b"public key",
+ capabilities: vec![],
+ rollback_index: 42,
+ };
+
+ #[test]
+ fn base_data_conversion() {
+ let vb_data = BASE_VB_DATA;
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+
+ assert_eq!(inputs.mode, DiceMode::kDiceModeNormal);
+ assert_eq!(inputs.security_version, 42);
+ assert!(!inputs.rkp_vm_marker);
+
+ // TODO(b/313608219): Consider checks for code_hash and possibly auth_hash.
+ }
+
+ #[test]
+ fn debuggable_conversion() {
+ let vb_data = VerifiedBootData { debug_level: DebugLevel::Full, ..BASE_VB_DATA };
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+
+ assert_eq!(inputs.mode, DiceMode::kDiceModeDebug);
+ }
+
+ #[test]
+ fn rkp_vm_conversion() {
+ let vb_data =
+ VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+
+ assert!(inputs.rkp_vm_marker);
+ }
+
+ #[test]
+ fn base_config_descriptor() {
+ let vb_data = BASE_VB_DATA;
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+ let config_map = decode_config_descriptor(&inputs);
+
+ assert_eq!(config_map.get(&COMPONENT_NAME_KEY).unwrap().as_text().unwrap(), "vm_entry");
+ assert_eq!(config_map.get(&COMPONENT_VERSION_KEY), None);
+ assert_eq!(config_map.get(&RESETTABLE_KEY), None);
+ if cfg!(dice_changes) {
+ assert_eq!(
+ config_map.get(&SECURITY_VERSION_KEY).unwrap().as_integer().unwrap(),
+ 42.into()
+ );
+ } else {
+ assert_eq!(config_map.get(&SECURITY_VERSION_KEY), None);
+ }
+ assert_eq!(config_map.get(&RKP_VM_MARKER_KEY), None);
+ }
+
+ #[test]
+ fn config_descriptor_with_rkp_vm() {
+ let vb_data =
+ VerifiedBootData { capabilities: vec![Capability::RemoteAttest], ..BASE_VB_DATA };
+ let inputs = PartialInputs::new(&vb_data).unwrap();
+ let config_map = decode_config_descriptor(&inputs);
+
+ assert!(config_map.get(&RKP_VM_MARKER_KEY).unwrap().is_null());
+ }
+
+ fn decode_config_descriptor(inputs: &PartialInputs) -> HashMap<i64, Value> {
+ let mut buffer = [0; 128];
+ let config_descriptor = inputs.generate_config_descriptor(&mut buffer).unwrap();
+
+ let cbor_map =
+ cbor_util::deserialize::<Value>(config_descriptor).unwrap().into_map().unwrap();
+
+ cbor_map
+ .into_iter()
+ .map(|(k, v)| ((k.into_integer().unwrap().try_into().unwrap()), v))
+ .collect()
+ }
+}
diff --git a/secretkeeper/comm/Android.bp b/secretkeeper/comm/Android.bp
deleted file mode 100644
index cb3e713..0000000
--- a/secretkeeper/comm/Android.bp
+++ /dev/null
@@ -1,52 +0,0 @@
-/*
- * 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
deleted file mode 100644
index ab6ca3f..0000000
--- a/secretkeeper/comm/src/cbor_convert.rs
+++ /dev/null
@@ -1,46 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 6a5e24f..0000000
--- a/secretkeeper/comm/src/data_types/error.rs
+++ /dev/null
@@ -1,94 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 096777f..0000000
--- a/secretkeeper/comm/src/data_types/mod.rs
+++ /dev/null
@@ -1,27 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 7a1e575..0000000
--- a/secretkeeper/comm/src/data_types/packet.rs
+++ /dev/null
@@ -1,118 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 0d54bcd..0000000
--- a/secretkeeper/comm/src/data_types/request.rs
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * 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
deleted file mode 100644
index a7d29cc..0000000
--- a/secretkeeper/comm/src/data_types/request_response_impl.rs
+++ /dev/null
@@ -1,97 +0,0 @@
-/*
- * 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
deleted file mode 100644
index e975ebc..0000000
--- a/secretkeeper/comm/src/data_types/response.rs
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 9a10ac0..0000000
--- a/secretkeeper/comm/src/lib.rs
+++ /dev/null
@@ -1,24 +0,0 @@
-/*
- * 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
deleted file mode 100644
index 68964fd..0000000
--- a/secretkeeper/comm/tests/data_types.rs
+++ /dev/null
@@ -1,132 +0,0 @@
-/*
- * 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/Android.bp b/secretkeeper/dice_policy/Android.bp
deleted file mode 100644
index 4f1e8b6..0000000
--- a/secretkeeper/dice_policy/Android.bp
+++ /dev/null
@@ -1,37 +0,0 @@
-package {
- default_applicable_licenses: ["Android-Apache-2.0"],
-}
-
-rust_defaults {
- name: "libdice_policy.defaults",
- crate_name: "dice_policy",
- defaults: ["avf_build_flags_rust"],
- srcs: ["src/lib.rs"],
- edition: "2021",
- prefer_rlib: true,
- rustlibs: [
- "libanyhow",
- "libciborium",
- "libcoset",
- "libnum_traits",
- ],
- proc_macros: ["libnum_derive"],
-}
-
-rust_library {
- name: "libdice_policy",
- defaults: ["libdice_policy.defaults"],
-}
-
-rust_test {
- name: "libdice_policy.test",
- defaults: [
- "libdice_policy.defaults",
- "rdroidtest.defaults",
- ],
- test_suites: ["general-tests"],
- rustlibs: [
- "librustutils",
- "libscopeguard",
- ],
-}
diff --git a/secretkeeper/dice_policy/src/lib.rs b/secretkeeper/dice_policy/src/lib.rs
deleted file mode 100644
index 076ba3b..0000000
--- a/secretkeeper/dice_policy/src/lib.rs
+++ /dev/null
@@ -1,504 +0,0 @@
-/*
- * 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.
- */
-
-//! A “DICE policy” is a format for setting constraints on a DICE chain. A DICE chain policy
-//! verifier takes a policy and a DICE chain, and returns a boolean indicating whether the
-//! DICE chain meets the constraints set out on a policy.
-//!
-//! This forms the foundation of Dice Policy aware Authentication (DPA-Auth), where the server
-//! authenticates a client by comparing its dice chain against a set policy.
-//!
-//! Another use is "sealing", where clients can use an appropriately constructed dice policy to
-//! seal a secret. Unsealing is only permitted if dice chain of the component requesting unsealing
-//! complies with the policy.
-//!
-//! A typical policy will assert things like:
-//! # DK_pub must have this value
-//! # The DICE chain must be exactly five certificates long
-//! # authorityHash in the third certificate must have this value
-//! securityVersion in the fourth certificate must be an integer greater than 8
-//!
-//! These constraints used to express policy are (for now) limited to following 2 types:
-//! 1. Exact Match: useful for enforcing rules like authority hash should be exactly equal.
-//! 2. Greater than or equal to: Useful for setting policies that seal
-//! Anti-rollback protected entities (should be accessible to versions >= present).
-//!
-//! Dice Policy CDDL:
-//!
-//! dicePolicy = [
-//! 1, ; dice policy version
-//! + nodeConstraintList ; for each entry in dice chain
-//! ]
-//!
-//! nodeConstraintList = [
-//! * nodeConstraint
-//! ]
-//!
-//! ; We may add a hashConstraint item later
-//! nodeConstraint = exactMatchConstraint / geConstraint
-//!
-//! exactMatchConstraint = [1, keySpec, value]
-//! geConstraint = [2, keySpec, int]
-//!
-//! keySpec = [value+]
-//!
-//! value = bool / int / tstr / bstr
-
-use anyhow::{anyhow, bail, ensure, Context, Result};
-use ciborium::Value;
-use coset::{AsCborValue, CoseSign1};
-use num_derive::FromPrimitive;
-use num_traits::FromPrimitive;
-use std::borrow::Cow;
-use std::iter::zip;
-
-const DICE_POLICY_VERSION: u64 = 1;
-
-/// Constraint Types supported in Dice policy.
-#[repr(u16)]
-#[non_exhaustive]
-#[derive(Clone, Copy, Debug, FromPrimitive, PartialEq)]
-pub enum ConstraintType {
- /// Enforce exact match criteria, indicating the policy should match
- /// if the dice chain has exact same specified values.
- ExactMatch = 1,
- /// Enforce Greater than or equal to criteria. When applied on security_version, this
- /// can be useful to set policy that matches dice chains with same or upgraded images.
- GreaterOrEqual = 2,
-}
-
-/// ConstraintSpec is used to specify which constraint type to apply and
-/// on which all entries in a dice node.
-/// See documentation of `from_dice_chain()` for examples.
-pub struct ConstraintSpec {
- constraint_type: ConstraintType,
- // path is essentially a list of label/int.
- // It identifies which entry (in a dice node) to be applying constraints on.
- path: Vec<i64>,
-}
-
-impl ConstraintSpec {
- /// Construct the ConstraintSpec.
- pub fn new(constraint_type: ConstraintType, path: Vec<i64>) -> Result<Self> {
- Ok(ConstraintSpec { constraint_type, path })
- }
-}
-
-// TODO(b/291238565): Restrict (nested_)key & value type to (bool/int/tstr/bstr).
-// and maybe convert it into struct.
-/// Each constraint (on a dice node) is a tuple: (ConstraintType, constraint_path, value)
-#[derive(Debug, PartialEq)]
-struct Constraint(u16, Vec<i64>, Value);
-
-/// List of all constraints on a dice node.
-#[derive(Debug, PartialEq)]
-struct NodeConstraints(Box<[Constraint]>);
-
-/// Module for working with dice policy.
-#[derive(Debug, PartialEq)]
-pub struct DicePolicy {
- version: u64,
- node_constraints_list: Box<[NodeConstraints]>, // Constraint on each entry in dice chain.
-}
-
-impl DicePolicy {
- /// Construct a dice policy from a given dice chain.
- /// This can be used by clients to construct a policy to seal secrets.
- /// Constraints on all but first dice node is applied using constraint_spec argument.
- /// For the first node (which is a ROT key), the constraint is ExactMatch of the whole node.
- ///
- /// # Arguments
- /// `dice_chain`: The serialized CBOR encoded Dice chain, adhering to Android Profile for DICE.
- /// https://pigweed.googlesource.com/open-dice/+/refs/heads/main/docs/android.md
- ///
- /// `constraint_spec`: List of constraints to be applied on dice node.
- /// Each constraint is a ConstraintSpec object.
- ///
- /// Note: Dice node is treated as a nested map (& so the lookup is done in that fashion).
- ///
- /// Examples of constraint_spec:
- /// 1. For exact_match on auth_hash & greater_or_equal on security_version
- /// constraint_spec =[
- /// (ConstraintType::ExactMatch, vec![AUTHORITY_HASH]),
- /// (ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME]),
- /// ];
- ///
- /// 2. For hypothetical (and highly simplified) dice chain:
- ///
- /// [ROT_KEY, [{1 : 'a', 2 : {200 : 5, 201 : 'b'}}]]
- /// The following can be used
- /// constraint_spec =[
- /// ConstraintSpec(ConstraintType::ExactMatch, vec![1]), // exact_matches value 'a'
- /// ConstraintSpec(ConstraintType::GreaterOrEqual, vec![2, 200]),// matches any value >= 5
- /// ];
- pub fn from_dice_chain(dice_chain: &[u8], constraint_spec: &[ConstraintSpec]) -> Result<Self> {
- let dice_chain = deserialize_dice_chain(dice_chain)?;
- let mut constraints_list: Vec<NodeConstraints> = Vec::with_capacity(dice_chain.len());
- let mut it = dice_chain.into_iter();
-
- constraints_list.push(NodeConstraints(Box::new([Constraint(
- ConstraintType::ExactMatch as u16,
- Vec::new(),
- it.next().unwrap(),
- )])));
-
- for (n, value) in it.enumerate() {
- let entry = cbor_value_from_cose_sign(value)
- .with_context(|| format!("Unable to get Cose payload at: {}", n))?;
- constraints_list.push(payload_to_constraints(entry, constraint_spec)?);
- }
-
- Ok(DicePolicy {
- version: DICE_POLICY_VERSION,
- node_constraints_list: constraints_list.into_boxed_slice(),
- })
- }
-
- /// Dice chain policy verifier - Compare the input dice chain against this Dice policy.
- /// The method returns Ok() if the dice chain meets the constraints set in Dice policy,
- /// otherwise returns error in case of mismatch.
- /// TODO(b/291238565) Create a separate error module for DicePolicy mismatches.
- pub fn matches_dice_chain(&self, dice_chain: &[u8]) -> Result<()> {
- let dice_chain = deserialize_dice_chain(dice_chain)?;
- ensure!(
- dice_chain.len() == self.node_constraints_list.len(),
- format!(
- "Dice chain size({}) does not match policy({})",
- dice_chain.len(),
- self.node_constraints_list.len()
- )
- );
-
- for (n, (dice_node, node_constraints)) in
- zip(dice_chain, self.node_constraints_list.iter()).enumerate()
- {
- let dice_node_payload = if n == 0 {
- dice_node
- } else {
- cbor_value_from_cose_sign(dice_node)
- .with_context(|| format!("Unable to get Cose payload at: {}", n))?
- };
- check_constraints_on_node(node_constraints, &dice_node_payload)
- .context(format!("Mismatch found at {}", n))?;
- }
- Ok(())
- }
-}
-
-fn check_constraints_on_node(node_constraints: &NodeConstraints, dice_node: &Value) -> Result<()> {
- for constraint in node_constraints.0.iter() {
- check_constraint_on_node(constraint, dice_node)?;
- }
- Ok(())
-}
-
-fn check_constraint_on_node(constraint: &Constraint, dice_node: &Value) -> Result<()> {
- let Constraint(cons_type, path, value_in_constraint) = constraint;
- let value_in_node = lookup_value_in_nested_map(dice_node, path)?;
- match ConstraintType::from_u16(*cons_type).ok_or(anyhow!("Unexpected Constraint type"))? {
- ConstraintType::ExactMatch => ensure!(value_in_node == *value_in_constraint),
- ConstraintType::GreaterOrEqual => {
- let value_in_node = value_in_node
- .as_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"))?;
- ensure!(value_in_node >= value_min);
- }
- };
- Ok(())
-}
-
-// Take the payload of a dice node & construct the constraints on it.
-fn payload_to_constraints(
- payload: Value,
- constraint_spec: &[ConstraintSpec],
-) -> Result<NodeConstraints> {
- let mut node_constraints: Vec<Constraint> = Vec::with_capacity(constraint_spec.len());
- for constraint_item in constraint_spec {
- let constraint_path = constraint_item.path.to_vec();
- if constraint_path.is_empty() {
- bail!("Expected non-empty key spec");
- }
- let val = lookup_value_in_nested_map(&payload, &constraint_path)
- .context(format!("Value not found for constraint_path {:?}", constraint_path))?;
- let constraint = Constraint(constraint_item.constraint_type as u16, constraint_path, val);
- node_constraints.push(constraint);
- }
- Ok(NodeConstraints(node_constraints.into_boxed_slice()))
-}
-
-// Lookup value corresponding to constraint path in nested map.
-// This function recursively calls itself.
-// The depth of recursion is limited by the size of constraint_path.
-fn lookup_value_in_nested_map(cbor_map: &Value, constraint_path: &[i64]) -> Result<Value> {
- if constraint_path.is_empty() {
- return Ok(cbor_map.clone());
- }
- let explicit_map = get_map_from_value(cbor_map)?;
- let val = lookup_value_in_map(&explicit_map, constraint_path[0])
- .ok_or(anyhow!("Value not found for constraint key: {:?}", constraint_path[0]))?;
- lookup_value_in_nested_map(val, &constraint_path[1..])
-}
-
-fn get_map_from_value(cbor_map: &Value) -> Result<Cow<Vec<(Value, Value)>>> {
- match cbor_map {
- Value::Bytes(b) => value_from_bytes(b)?
- .into_map()
- .map(Cow::Owned)
- .map_err(|e| anyhow!("Expected a CBOR map: {:?}", e)),
- Value::Map(map) => Ok(Cow::Borrowed(map)),
- _ => bail!("Expected a CBOR map {:?}", cbor_map),
- }
-}
-
-fn lookup_value_in_map(map: &[(Value, Value)], key: i64) -> Option<&Value> {
- let key = Value::Integer(key.into());
- for (k, v) in map.iter() {
- if k == &key {
- return Some(v);
- }
- }
- None
-}
-
-/// Extract the payload from the COSE Sign
-fn cbor_value_from_cose_sign(cbor: Value) -> Result<Value> {
- let sign1 =
- CoseSign1::from_cbor_value(cbor).map_err(|e| anyhow!("Error extracting CoseKey: {}", e))?;
- match sign1.payload {
- None => bail!("Missing payload"),
- Some(payload) => Ok(value_from_bytes(&payload)?),
- }
-}
-fn deserialize_dice_chain(dice_chain_bytes: &[u8]) -> Result<Vec<Value>> {
- // TODO(b/298217847): Check if the given dice chain adheres to Explicit-key DiceCertChain
- // format and if not, convert it.
- let dice_chain =
- value_from_bytes(dice_chain_bytes).context("Unable to decode top-level CBOR")?;
- let dice_chain = match dice_chain {
- Value::Array(array) if array.len() >= 2 => array,
- _ => bail!("Expected an array of at least length 2, found: {:?}", dice_chain),
- };
- Ok(dice_chain)
-}
-
-/// Decodes the provided binary CBOR-encoded value and returns a
-/// ciborium::Value struct wrapped in Result.
-fn value_from_bytes(mut bytes: &[u8]) -> Result<Value> {
- let value = ciborium::de::from_reader(&mut bytes)?;
- // Ciborium tries to read one Value, & doesn't care if there is trailing data after it. We do.
- if !bytes.is_empty() {
- bail!("Unexpected trailing data while converting to CBOR value");
- }
- Ok(value)
-}
-
-#[cfg(test)]
-rdroidtest::test_main!();
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use ciborium::cbor;
- use coset::{CoseKey, Header, ProtectedHeader};
- use rdroidtest::test;
-
- const AUTHORITY_HASH: i64 = -4670549;
- const CONFIG_DESC: i64 = -4670548;
- const COMPONENT_NAME: i64 = -70002;
- const KEY_MODE: i64 = -4670551;
-
- // Helper struct to encapsulate artifacts that are useful for unit tests.
- struct TestArtifacts {
- // A dice chain.
- input_dice: Vec<u8>,
- // A list of ConstraintSpec that can be applied on the input_dice to get a dice policy.
- constraint_spec: Vec<ConstraintSpec>,
- // The expected dice policy if above constraint_spec is applied to input_dice.
- expected_dice_policy: DicePolicy,
- // Another dice chain, which is almost same as the input_dice, but (roughly) imitates
- // an 'updated' one, ie, some int entries are higher than corresponding entry
- // in input_chain.
- updated_input_dice: Vec<u8>,
- }
-
- impl TestArtifacts {
- // Get an example instance of TestArtifacts. This uses a hard coded, hypothetical
- // chain of certificates & a list of constraint_spec on this.
- fn get_example() -> Self {
- const EXAMPLE_NUM_1: i64 = 59765;
- const EXAMPLE_NUM_2: i64 = 59766;
- const EXAMPLE_STRING: &str = "testing_dice_policy";
- const UNCONSTRAINED_STRING: &str = "unconstrained_string";
- const ANOTHER_UNCONSTRAINED_STRING: &str = "another_unconstrained_string";
-
- let rot_key = CoseKey::default().to_cbor_value().unwrap();
- let input_dice = Self::get_dice_chain_helper(
- rot_key.clone(),
- EXAMPLE_NUM_1,
- EXAMPLE_STRING,
- UNCONSTRAINED_STRING,
- );
-
- // Now construct constraint_spec on the input dice, note this will use the keys
- // which are also hardcoded within the get_dice_chain_helper.
-
- let constraint_spec = vec![
- ConstraintSpec::new(ConstraintType::ExactMatch, vec![1]).unwrap(),
- // Notice how key "2" is (deliberately) absent in ConstraintSpec
- // so policy should not constrain it.
- ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![3, 100]).unwrap(),
- ];
- let expected_dice_policy = DicePolicy {
- version: 1,
- node_constraints_list: Box::new([
- NodeConstraints(Box::new([Constraint(
- ConstraintType::ExactMatch as u16,
- vec![],
- rot_key.clone(),
- )])),
- NodeConstraints(Box::new([
- Constraint(
- ConstraintType::ExactMatch as u16,
- vec![1],
- Value::Text(EXAMPLE_STRING.to_string()),
- ),
- Constraint(
- ConstraintType::GreaterOrEqual as u16,
- vec![3, 100],
- Value::from(EXAMPLE_NUM_1),
- ),
- ])),
- ]),
- };
-
- let updated_input_dice = Self::get_dice_chain_helper(
- rot_key.clone(),
- EXAMPLE_NUM_2,
- EXAMPLE_STRING,
- ANOTHER_UNCONSTRAINED_STRING,
- );
- Self { input_dice, constraint_spec, expected_dice_policy, updated_input_dice }
- }
-
- // Helper method method to generate a dice chain with a given rot_key.
- // Other arguments are ad-hoc values in the nested map. Callers use these to
- // construct appropriate constrains in dice policies.
- fn get_dice_chain_helper(
- rot_key: Value,
- version: i64,
- constrained_string: &str,
- unconstrained_string: &str,
- ) -> Vec<u8> {
- let nested_payload = cbor!({
- 100 => version
- })
- .unwrap();
-
- let payload = cbor!({
- 1 => constrained_string,
- 2 => unconstrained_string,
- 3 => Value::Bytes(value_to_bytes(&nested_payload).unwrap()),
- })
- .unwrap();
- let payload = value_to_bytes(&payload).unwrap();
- let dice_node = CoseSign1 {
- protected: ProtectedHeader::default(),
- unprotected: Header::default(),
- payload: Some(payload),
- signature: b"ddef".to_vec(),
- }
- .to_cbor_value()
- .unwrap();
- let input_dice = Value::Array([rot_key.clone(), dice_node].to_vec());
-
- value_to_bytes(&input_dice).unwrap()
- }
- }
-
- test!(policy_structure_check);
- fn policy_structure_check() {
- let example = TestArtifacts::get_example();
- let policy =
- DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec).unwrap();
-
- // Assert policy is exactly as expected!
- assert_eq!(policy, example.expected_dice_policy);
- }
-
- test!(policy_matches_original_dice_chain);
- fn policy_matches_original_dice_chain() {
- let example = TestArtifacts::get_example();
- assert!(
- DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
- .unwrap()
- .matches_dice_chain(&example.input_dice)
- .is_ok(),
- "The dice chain did not match the policy constructed out of it!"
- );
- }
-
- test!(policy_matches_updated_dice_chain);
- fn policy_matches_updated_dice_chain() {
- let example = TestArtifacts::get_example();
- assert!(
- DicePolicy::from_dice_chain(&example.input_dice, &example.constraint_spec)
- .unwrap()
- .matches_dice_chain(&example.updated_input_dice)
- .is_ok(),
- "The updated dice chain did not match the original policy!"
- );
- }
-
- test!(policy_mismatch_downgraded_dice_chain);
- fn policy_mismatch_downgraded_dice_chain() {
- let example = TestArtifacts::get_example();
- assert!(
- DicePolicy::from_dice_chain(&example.updated_input_dice, &example.constraint_spec)
- .unwrap()
- .matches_dice_chain(&example.input_dice)
- .is_err(),
- "The (downgraded) dice chain matched the policy constructed out of the 'updated'\
- dice chain!!"
- );
- }
-
- test!(policy_dice_size_is_same);
- fn policy_dice_size_is_same() {
- // This is the number of certs in compos bcc (including the first ROT)
- // To analyze a bcc use hwtrust tool from /tools/security/remote_provisioning/hwtrust
- // `hwtrust --verbose dice-chain [path]/composbcc`
- let compos_dice_chain_size: usize = 5;
- let input_dice = include_bytes!("../testdata/composbcc");
- let constraint_spec = [
- ConstraintSpec::new(ConstraintType::ExactMatch, vec![AUTHORITY_HASH]).unwrap(),
- ConstraintSpec::new(ConstraintType::ExactMatch, vec![KEY_MODE]).unwrap(),
- ConstraintSpec::new(ConstraintType::GreaterOrEqual, vec![CONFIG_DESC, COMPONENT_NAME])
- .unwrap(),
- ];
- let policy = DicePolicy::from_dice_chain(input_dice, &constraint_spec).unwrap();
- assert_eq!(policy.node_constraints_list.len(), compos_dice_chain_size);
- }
-
- /// Encodes a ciborium::Value into bytes.
- fn value_to_bytes(value: &Value) -> Result<Vec<u8>> {
- let mut bytes: Vec<u8> = Vec::new();
- ciborium::ser::into_writer(&value, &mut bytes)?;
- Ok(bytes)
- }
-}
diff --git a/secretkeeper/dice_policy/testdata/composbcc b/secretkeeper/dice_policy/testdata/composbcc
deleted file mode 100644
index fb3e006..0000000
--- a/secretkeeper/dice_policy/testdata/composbcc
+++ /dev/null
Binary files differ
diff --git a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
index 886ca81..60f3e52 100644
--- a/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
+++ b/tests/hostside/java/com/android/microdroid/test/MicrodroidHostTests.java
@@ -733,19 +733,10 @@
.isLessThan(atomVmExited.getElapsedTimeMillis());
}
- @Test
- @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
- public void testMicrodroidBoots() throws Exception {
+ private void testMicrodroidBootsWithBuilder(MicrodroidBuilder builder) throws Exception {
CommandRunner android = new CommandRunner(getDevice());
- final String configPath = "assets/vm_config.json"; // path inside the APK
- mMicrodroidDevice =
- MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
- .debugLevel("full")
- .memoryMib(minMemorySize())
- .cpuTopology("match_host")
- .protectedVm(mProtectedVm)
- .build(getAndroidDevice());
+ mMicrodroidDevice = builder.build(getAndroidDevice());
mMicrodroidDevice.waitForBootComplete(BOOT_COMPLETE_TIMEOUT);
CommandRunner microdroid = new CommandRunner(mMicrodroidDevice);
@@ -809,6 +800,35 @@
}
@Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
+ public void testMicrodroidBoots() throws Exception {
+ final String configPath = "assets/vm_config.json"; // path inside the APK
+ testMicrodroidBootsWithBuilder(
+ MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+ .debugLevel("full")
+ .memoryMib(minMemorySize())
+ .cpuTopology("match_host")
+ .protectedVm(mProtectedVm));
+ }
+
+ @Test
+ @CddTest(requirements = {"9.17/C-1-1", "9.17/C-1-2", "9.17/C/1-3"})
+ public void testMicrodroidBootsWithGki() throws Exception {
+ List<String> supportedVersions = getSupportedGKIVersions();
+ assumeFalse("no available gki", supportedVersions.isEmpty());
+ for (String ver : supportedVersions) {
+ final String configPath = "assets/vm_config.json"; // path inside the APK
+ testMicrodroidBootsWithBuilder(
+ MicrodroidBuilder.fromDevicePath(getPathForPackage(PACKAGE_NAME), configPath)
+ .debugLevel("full")
+ .memoryMib(minMemorySize())
+ .cpuTopology("match_host")
+ .protectedVm(mProtectedVm)
+ .gki(ver));
+ }
+ }
+
+ @Test
public void testMicrodroidRamUsage() throws Exception {
final String configPath = "assets/vm_config.json";
mMicrodroidDevice =
@@ -1031,21 +1051,28 @@
&& device.doesFileExist("/sys/bus/platform/drivers/vfio-platform"));
}
- private List<String> getAssignableDevices() throws Exception {
+ private List<String> parseStringArrayFieldsFromVmInfo(String header) throws Exception {
CommandRunner android = new CommandRunner(getDevice());
String result = android.run("/apex/com.android.virt/bin/vm", "info");
- List<String> devices = new ArrayList<>();
+ List<String> ret = new ArrayList<>();
for (String line : result.split("\n")) {
- final String header = "Assignable devices: ";
if (!line.startsWith(header)) continue;
JSONArray jsonArray = new JSONArray(line.substring(header.length()));
for (int i = 0; i < jsonArray.length(); i++) {
- devices.add(jsonArray.getString(i));
+ ret.add(jsonArray.getString(i));
}
break;
}
- return devices;
+ return ret;
+ }
+
+ private List<String> getAssignableDevices() throws Exception {
+ return parseStringArrayFieldsFromVmInfo("Assignable devices: ");
+ }
+
+ private List<String> getSupportedGKIVersions() throws Exception {
+ return parseStringArrayFieldsFromVmInfo("Available gki versions: ");
}
private TestDevice getAndroidDevice() {