aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-04-30 23:02:19 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-04-30 23:02:19 +0000
commit605ae13dda96ebdfb651956a9c50f8ae3ef38eb8 (patch)
treeb6725d91c88dd8c52e9efa011d743a62d1c093dd
parent0e138ecf18a33795a3fb84dbf1115ee5397a3f15 (diff)
parentf9d4620d48b2a2b7310bbb56c0c8af821964c5f1 (diff)
downloadavb-sdk-release.tar.gz
Snap for 11785460 from f9d4620d48b2a2b7310bbb56c0c8af821964c5f1 to sdk-releasesdk-release
Change-Id: I39719f58c8dc2721dda43161900cc961c81c8af7
-rw-r--r--rust/Android.bp38
-rw-r--r--rust/src/cert.rs177
-rw-r--r--rust/src/error.rs41
-rw-r--r--rust/src/lib.rs6
-rw-r--r--rust/src/ops.rs188
-rw-r--r--rust/tests/cert_tests.rs289
-rw-r--r--rust/tests/test_data.rs73
-rw-r--r--rust/tests/test_ops.rs187
-rw-r--r--rust/tests/tests.rs37
-rw-r--r--rust/tests/verify_tests.rs174
10 files changed, 982 insertions, 228 deletions
diff --git a/rust/Android.bp b/rust/Android.bp
index c390553..9188819 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -35,13 +35,16 @@ rust_defaults {
"--default-enum-style rust",
"--with-derive-default",
"--with-derive-custom=Avb.*Descriptor=FromZeroes,FromBytes",
+ "--with-derive-custom=AvbCertPermanentAttributes=FromZeroes,FromBytes,AsBytes",
+ "--with-derive-custom=AvbCertCertificate.*=FromZeroes,FromBytes,AsBytes",
+ "--with-derive-custom=AvbCertUnlock.*=FromZeroes,FromBytes,AsBytes",
"--allowlist-type=AvbDescriptorTag",
"--allowlist-type=Avb.*Flags",
"--allowlist-function=.*",
"--allowlist-var=AVB.*",
"--use-core",
"--raw-line=#![no_std]",
- "--raw-line=use zerocopy::{FromZeroes, FromBytes};",
+ "--raw-line=use zerocopy::{AsBytes, FromBytes, FromZeroes};",
"--ctypes-prefix=core::ffi",
],
cflags: ["-DBORINGSSL_NO_CXX"],
@@ -52,7 +55,7 @@ rust_defaults {
name: "libavb_bindgen.std.defaults",
defaults: ["libavb_bindgen.common.defaults"],
host_supported: true,
- static_libs: ["libavb"],
+ static_libs: ["libavb_cert"],
shared_libs: ["libcrypto"],
rustlibs: ["libzerocopy"],
apex_available: ["com.android.virt"],
@@ -63,7 +66,7 @@ rust_defaults {
name: "libavb_bindgen.nostd.defaults",
defaults: ["libavb_bindgen.common.defaults"],
static_libs: [
- "libavb_baremetal",
+ "libavb_cert_baremetal",
"libcrypto_baremetal",
],
rustlibs: ["libzerocopy_nostd_noalloc"],
@@ -161,7 +164,7 @@ rust_defaults {
"libzerocopy_nostd_noalloc",
],
whole_static_libs: [
- "libavb_baremetal",
+ "libavb_cert_baremetal",
],
stdlibs: [
"libcore.rust_sysroot",
@@ -178,7 +181,7 @@ rust_defaults {
"libzerocopy",
],
whole_static_libs: [
- "libavb",
+ "libavb_cert",
],
}
@@ -246,7 +249,7 @@ rust_library {
"libavb_rs",
],
whole_static_libs: [
- "libavb",
+ "libavb_cert",
],
}
@@ -290,6 +293,9 @@ rust_defaults {
name: "libavb_rs_test.defaults",
srcs: ["tests/tests.rs"],
data: [
+ ":avb_cert_test_permanent_attributes",
+ ":avb_cert_test_unlock_challenge",
+ ":avb_cert_test_unlock_credential",
":avb_testkey_rsa4096_pub_bin",
":avb_testkey_rsa8192_pub_bin",
":avbrs_test_image",
@@ -298,13 +304,17 @@ rust_defaults {
":avbrs_test_image_with_vbmeta_footer_for_test_part_2",
":avbrs_test_vbmeta",
":avbrs_test_vbmeta_2_parts",
+ ":avbrs_test_vbmeta_cert",
":avbrs_test_vbmeta_persistent_digest",
":avbrs_test_vbmeta_with_chained_partition",
":avbrs_test_vbmeta_with_commandline",
":avbrs_test_vbmeta_with_hashtree",
":avbrs_test_vbmeta_with_property",
],
- rustlibs: ["libhex"],
+ rustlibs: [
+ "libhex",
+ "libzerocopy",
+ ],
test_suites: ["general-tests"],
clippy_lints: "android",
lints: "android",
@@ -390,6 +400,20 @@ genrule {
cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)",
}
+// Standalone vbmeta image signing the test image descriptor with
+// `avb_cert_testkey_psk` and `avb_cert_test_metadata`.
+genrule {
+ name: "avbrs_test_vbmeta_cert",
+ tools: ["avbtool"],
+ srcs: [
+ ":avbrs_test_image_descriptor",
+ ":avb_cert_test_metadata",
+ ":avb_cert_testkey_psk",
+ ],
+ out: ["test_vbmeta_cert.img"],
+ cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_cert_testkey_psk) --public_key_metadata $(location :avb_cert_test_metadata) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)",
+}
+
// Standalone vbmeta image signing the test image descriptors for "test_part" and "test_part_2".
genrule {
name: "avbrs_test_vbmeta_2_parts",
diff --git a/rust/src/cert.rs b/rust/src/cert.rs
index 52be5bf..aa54859 100644
--- a/rust/src/cert.rs
+++ b/rust/src/cert.rs
@@ -83,7 +83,14 @@
//! # Internally, the device calls `cert_validate_unlock_credential()` to verify the credential.
//! ```
-use crate::{IoError, IoResult, Ops};
+use crate::{error::io_enum_to_result, ops, IoError, IoResult, Ops, PublicKeyForPartitionInfo};
+use avb_bindgen::{
+ avb_cert_generate_unlock_challenge, avb_cert_validate_unlock_credential,
+ avb_cert_validate_vbmeta_public_key,
+};
+use core::{ffi::CStr, pin::pin};
+#[cfg(feature = "uuid")]
+use uuid::Uuid;
/// libavb_cert permanent attributes.
pub use avb_bindgen::AvbCertPermanentAttributes as CertPermanentAttributes;
@@ -203,14 +210,41 @@ pub trait CertOps {
/// * `public_key_metadata`: public key metadata.
///
/// # Returns
-/// True if the given key is valid, false if it is not, `IoError` on error.
+/// * `Ok(true)` if the given key is valid according to the permanent attributes.
+/// * `Ok(false)` if the given key is invalid.
+/// * `Err(IoError::NotImplemented)` if `ops` does not provide the required `cert_ops()`.
+/// * `Err(IoError)` on `ops` callback error.
pub fn cert_validate_vbmeta_public_key(
- _ops: &mut dyn Ops,
- _public_key: &[u8],
- _public_key_metadata: Option<&[u8]>,
+ ops: &mut dyn Ops,
+ public_key: &[u8],
+ public_key_metadata: Option<&[u8]>,
) -> IoResult<bool> {
- // TODO(b/320543206): implement
- Err(IoError::NotImplemented)
+ // This API requires both AVB and cert ops.
+ if ops.cert_ops().is_none() {
+ return Err(IoError::NotImplemented);
+ }
+
+ let ops_bridge = pin!(ops::OpsBridge::new(ops));
+ let public_key_metadata = public_key_metadata.unwrap_or(&[]);
+ let mut trusted = false;
+ io_enum_to_result(
+ // SAFETY:
+ // * `ops_bridge.init_and_get_c_ops()` gives us a valid `AvbOps` with cert.
+ // * `public_key` args are C-compatible pointer + size byte buffers.
+ // * `trusted` is a C-compatible bool.
+ // * this function does not retain references to any of these arguments.
+ unsafe {
+ avb_cert_validate_vbmeta_public_key(
+ ops_bridge.init_and_get_c_ops(),
+ public_key.as_ptr(),
+ public_key.len(),
+ public_key_metadata.as_ptr(),
+ public_key_metadata.len(),
+ &mut trusted,
+ )
+ },
+ )?;
+ Ok(trusted)
}
/// Generates a challenge for authenticated unlock.
@@ -220,15 +254,29 @@ pub fn cert_validate_vbmeta_public_key(
/// The user can sign the resulting token via `avbtool make_cert_unlock_credential`.
///
/// # Arguments
-/// * `cert_ops`: the `CertOps` callback implementations.
+/// * `cert_ops`: the `CertOps` callback implementations; base `Ops` are not required here.
///
/// # Returns
/// The challenge to sign with the PUK, or `IoError` on `cert_ops` failure.
-pub fn cert_generate_unlock_challenge(
- _cert_ops: &mut dyn CertOps,
-) -> IoResult<CertUnlockChallenge> {
- // TODO(b/320543206): implement
- Err(IoError::NotImplemented)
+pub fn cert_generate_unlock_challenge(cert_ops: &mut dyn CertOps) -> IoResult<CertUnlockChallenge> {
+ // `OpsBridge` requires a full `Ops` object, so we wrap `cert_ops` in a do-nothing `Ops`
+ // implementation. This is simpler than teaching `OpsBridge` to handle the cert-only case.
+ let mut ops = CertOnlyOps { cert_ops };
+ let ops_bridge = pin!(ops::OpsBridge::new(&mut ops));
+ let mut challenge = CertUnlockChallenge::default();
+ io_enum_to_result(
+ // SAFETY:
+ // * `ops_bridge.init_and_get_c_ops()` gives us a valid `AvbOps` with cert.
+ // * `challenge` is a valid C-compatible `CertUnlockChallenge`.
+ // * this function does not retain references to any of these arguments.
+ unsafe {
+ avb_cert_generate_unlock_challenge(
+ ops_bridge.init_and_get_c_ops().cert_ops,
+ &mut challenge,
+ )
+ },
+ )?;
+ Ok(challenge)
}
/// Validates a signed credential for authenticated unlock.
@@ -243,14 +291,109 @@ pub fn cert_generate_unlock_challenge(
/// # Returns
/// * `Ok(true)` if the credential validated
/// * `Ok(false)` if it failed validation
+/// * `Err(IoError::NotImplemented)` if `ops` does not provide the required `cert_ops()`.
/// * `Err(IoError)` on `ops` failure
pub fn cert_validate_unlock_credential(
// Note: in the libavb C API this function takes an `AvbCertOps` rather than `AvbOps`, but
// the implementation requires both, so we need an `Ops` here. This is also more consistent
// with `validate_vbmeta_public_key()` which similarly requires both but takes `AvbOps`.
- _ops: &mut dyn Ops,
- _credential: &CertUnlockCredential,
+ ops: &mut dyn Ops,
+ credential: &CertUnlockCredential,
) -> IoResult<bool> {
- // TODO(b/320543206): implement
- Err(IoError::NotImplemented)
+ // This API requires both AVB and cert ops.
+ if ops.cert_ops().is_none() {
+ return Err(IoError::NotImplemented);
+ }
+
+ let ops_bridge = pin!(ops::OpsBridge::new(ops));
+ let mut trusted = false;
+ io_enum_to_result(
+ // SAFETY:
+ // * `ops_bridge.init_and_get_c_ops()` gives us a valid `AvbOps` with cert.
+ // * `credential` is a valid C-compatible `CertUnlockCredential`.
+ // * `trusted` is a C-compatible bool.
+ // * this function does not retain references to any of these arguments.
+ unsafe {
+ avb_cert_validate_unlock_credential(
+ ops_bridge.init_and_get_c_ops().cert_ops,
+ credential,
+ &mut trusted,
+ )
+ },
+ )?;
+ Ok(trusted)
+}
+
+/// An `Ops` implementation that only provides the `cert_ops()` callback.
+struct CertOnlyOps<'a> {
+ cert_ops: &'a mut dyn CertOps,
+}
+
+impl<'a> Ops<'static> for CertOnlyOps<'a> {
+ fn read_from_partition(
+ &mut self,
+ _partition: &CStr,
+ _offset: i64,
+ _buffer: &mut [u8],
+ ) -> IoResult<usize> {
+ Err(IoError::NotImplemented)
+ }
+
+ fn validate_vbmeta_public_key(
+ &mut self,
+ _public_key: &[u8],
+ _public_key_metadata: Option<&[u8]>,
+ ) -> IoResult<bool> {
+ Err(IoError::NotImplemented)
+ }
+
+ fn read_rollback_index(&mut self, _rollback_index_location: usize) -> IoResult<u64> {
+ Err(IoError::NotImplemented)
+ }
+
+ 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> {
+ Err(IoError::NotImplemented)
+ }
+
+ #[cfg(feature = "uuid")]
+ fn get_unique_guid_for_partition(&mut self, _partition: &CStr) -> IoResult<Uuid> {
+ Err(IoError::NotImplemented)
+ }
+
+ fn get_size_of_partition(&mut self, _partition: &CStr) -> IoResult<u64> {
+ Err(IoError::NotImplemented)
+ }
+
+ 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)
+ }
+
+ fn cert_ops(&mut self) -> Option<&mut dyn CertOps> {
+ Some(self.cert_ops)
+ }
}
diff --git a/rust/src/error.rs b/rust/src/error.rs
index f60702b..a5642c6 100644
--- a/rust/src/error.rs
+++ b/rust/src/error.rs
@@ -218,6 +218,27 @@ pub(crate) fn result_to_io_enum(result: IoResult<()>) -> AvbIOResult {
result.map_or_else(|e| e.into(), |_| AvbIOResult::AVB_IO_RESULT_OK)
}
+/// Converts a bindgen `AvbIOResult` enum to an `IoResult<>`, mapping `AVB_IO_RESULT_OK` to the Rust
+/// equivalent `Ok(())` and errors to the corresponding `Err(IoError)`.
+///
+/// This function is also important to serve as a compile-time check that we're handling all the
+/// libavb enums; if a new one is added to (or removed from) the C code, this will fail to compile
+/// until it is updated to match.
+pub(crate) fn io_enum_to_result(result: AvbIOResult) -> IoResult<()> {
+ match result {
+ AvbIOResult::AVB_IO_RESULT_OK => Ok(()),
+ AvbIOResult::AVB_IO_RESULT_ERROR_OOM => Err(IoError::Oom),
+ AvbIOResult::AVB_IO_RESULT_ERROR_IO => Err(IoError::Io),
+ AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION => Err(IoError::NoSuchPartition),
+ AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION => {
+ Err(IoError::RangeOutsidePartition)
+ }
+ AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE => Err(IoError::NoSuchValue),
+ AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE => Err(IoError::InvalidValueSize),
+ AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE => Err(IoError::InsufficientSpace(0)),
+ }
+}
+
/// `AvbVBMetaVerifyResult` error wrapper.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum VbmetaVerifyError {
@@ -317,21 +338,11 @@ mod tests {
// This is a compile-time check that we handle all the `AvbIOResult` enum values. If any
// enums are added or removed this will break, indicating we need to update `IoError` to
// match.
- assert!(match AvbIOResult::AVB_IO_RESULT_OK {
- AvbIOResult::AVB_IO_RESULT_OK => Ok(()),
- AvbIOResult::AVB_IO_RESULT_ERROR_OOM => Err(IoError::Oom),
- AvbIOResult::AVB_IO_RESULT_ERROR_IO => Err(IoError::Io),
- AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION => Err(IoError::NoSuchPartition),
- AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION => {
- Err(IoError::RangeOutsidePartition)
- }
- AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE => Err(IoError::NoSuchValue),
- AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE => Err(IoError::InvalidValueSize),
- AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE => {
- Err(IoError::InsufficientSpace(0))
- }
- }
- .is_ok());
+ assert_eq!(io_enum_to_result(AvbIOResult::AVB_IO_RESULT_OK), Ok(()));
+ assert_eq!(
+ io_enum_to_result(AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION),
+ Err(IoError::NoSuchPartition)
+ );
}
#[test]
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 16bf066..99962ab 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -33,9 +33,9 @@ mod ops;
mod verify;
pub use cert::{
- cert_generate_unlock_challenge, cert_validate_unlock_credential, cert_validate_vbmeta_public_key,
- CertOps, CertPermanentAttributes, CERT_PIK_VERSION_LOCATION, CERT_PSK_VERSION_LOCATION,
- SHA256_DIGEST_SIZE,
+ cert_generate_unlock_challenge, cert_validate_unlock_credential,
+ cert_validate_vbmeta_public_key, CertOps, CertPermanentAttributes, CertUnlockChallenge,
+ CertUnlockCredential, CERT_PIK_VERSION_LOCATION, CERT_PSK_VERSION_LOCATION, SHA256_DIGEST_SIZE,
};
pub use descriptor::{
ChainPartitionDescriptor, ChainPartitionDescriptorFlags, Descriptor, DescriptorError,
diff --git a/rust/src/ops.rs b/rust/src/ops.rs
index 88e9d95..d8c1b16 100644
--- a/rust/src/ops.rs
+++ b/rust/src/ops.rs
@@ -19,8 +19,8 @@
extern crate alloc;
-use crate::{error::result_to_io_enum, CertOps, IoError, IoResult};
-use avb_bindgen::{AvbCertOps, AvbIOResult, AvbOps};
+use crate::{error::result_to_io_enum, CertOps, IoError, IoResult, SHA256_DIGEST_SIZE};
+use avb_bindgen::{AvbCertOps, AvbCertPermanentAttributes, AvbIOResult, AvbOps};
use core::{
cmp::min,
ffi::{c_char, c_void, CStr},
@@ -250,6 +250,10 @@ pub trait Ops<'a> {
/// Commonly when using certs the same struct will implement both `Ops` and `CertOps`, in which
/// case this can just return `Some(self)`.
///
+ /// Note: changing this return value in the middle of a libavb operation (e.g. from another
+ /// callback) is not recommended; it may cause runtime errors or a panic later on in the
+ /// operation. It's fine to change this return value outside of libavb operations.
+ ///
/// # Returns
/// The `CertOps` object, or `None` if not supported.
fn cert_ops(&mut self) -> Option<&mut dyn CertOps> {
@@ -323,11 +327,10 @@ impl<'o, 'p> OpsBridge<'o, 'p> {
},
cert_ops: AvbCertOps {
ops: ptr::null_mut(), // Set at the time of use.
- // TODO(b/320543206): implement the callbacks.
- read_permanent_attributes: None,
- read_permanent_attributes_hash: None,
- set_key_version: None,
- get_random: None,
+ read_permanent_attributes: Some(read_permanent_attributes),
+ read_permanent_attributes_hash: Some(read_permanent_attributes_hash),
+ set_key_version: Some(set_key_version),
+ get_random: Some(get_random),
},
rust_ops: ops,
_pin: PhantomPinned,
@@ -410,6 +413,23 @@ unsafe fn as_ops<'o, 'p>(avb_ops: *mut AvbOps) -> IoResult<&'o mut dyn Ops<'p>>
Ok(unsafe { bridge.as_mut() }.ok_or(IoError::Io)?.rust_ops)
}
+/// Similar to `as_ops()`, but for `CertOps`.
+///
+/// # Safety
+/// Same as `as_ops()`.
+unsafe fn as_cert_ops<'o>(cert_ops: *mut AvbCertOps) -> IoResult<&'o mut dyn CertOps> {
+ // SAFETY: we created this `CertOps` object and passed it to libavb so we know it meets all
+ // the criteria for `as_mut()`.
+ let cert_ops = unsafe { cert_ops.as_mut() }.ok_or(IoError::Io)?;
+
+ // SAFETY: caller must adhere to `as_ops()` safety requirements.
+ let ops = unsafe { as_ops(cert_ops.ops) }?;
+
+ // Return the `CertOps` implementation. If it doesn't exist here, it indicates an internal error
+ // in this library; somewhere we accepted a non-cert `Ops` into a function that requires cert.
+ ops.cert_ops().ok_or(IoError::NotImplemented)
+}
+
/// Converts a non-NULL `ptr` to `()`, NULL to `Err(IoError::Io)`.
fn check_nonnull<T>(ptr: *const T) -> IoResult<()> {
match ptr.is_null() {
@@ -1167,3 +1187,157 @@ unsafe fn try_validate_public_key_for_partition(
}
Ok(())
}
+
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
+///
+/// See corresponding `try_*` function docs.
+unsafe extern "C" fn read_permanent_attributes(
+ cert_ops: *mut AvbCertOps,
+ attributes: *mut AvbCertPermanentAttributes,
+) -> AvbIOResult {
+ result_to_io_enum(
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe { try_read_permanent_attributes(cert_ops, attributes) },
+ )
+}
+
+/// Bounces the C callback into the user-provided Rust implementation.
+///
+/// # Safety
+/// * `cert_ops` must have been created via `ScopedAvbOps`.
+/// * `attributes` must be a valid `AvbCertPermanentAttributes` that we have exclusive access to.
+unsafe fn try_read_permanent_attributes(
+ cert_ops: *mut AvbCertOps,
+ attributes: *mut AvbCertPermanentAttributes,
+) -> IoResult<()> {
+ // SAFETY: `attributes` is a valid object provided by libavb that we have exclusive access to.
+ let attributes = unsafe { attributes.as_mut() }.ok_or(IoError::Io)?;
+
+ // SAFETY:
+ // * we only use `cert_ops` objects created via `ScopedAvbOps` as required.
+ // * `cert_ops` is only extracted once and is dropped at the end of the callback.
+ let cert_ops = unsafe { as_cert_ops(cert_ops) }?;
+ cert_ops.read_permanent_attributes(attributes)
+}
+
+/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb.
+///
+/// See corresponding `try_*` function docs.
+unsafe extern "C" fn read_permanent_attributes_hash(
+ cert_ops: *mut AvbCertOps,
+ hash: *mut u8,
+) -> AvbIOResult {
+ result_to_io_enum(
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe { try_read_permanent_attributes_hash(cert_ops, hash) },
+ )
+}
+
+/// Bounces the C callback into the user-provided Rust implementation.
+///
+/// # Safety
+/// * `cert_ops` must have been created via `ScopedAvbOps`.
+/// * `hash` must point to a valid buffer of size `SHA256_DIGEST_SIZE` that we have exclusive
+/// access to.
+unsafe fn try_read_permanent_attributes_hash(
+ cert_ops: *mut AvbCertOps,
+ hash: *mut u8,
+) -> IoResult<()> {
+ check_nonnull(hash)?;
+
+ // SAFETY:
+ // * we only use `cert_ops` objects created via `ScopedAvbOps` as required.
+ // * `cert_ops` is only extracted once and is dropped at the end of the callback.
+ let cert_ops = unsafe { as_cert_ops(cert_ops) }?;
+ let provided_hash = cert_ops.read_permanent_attributes_hash()?;
+
+ // SAFETY:
+ // * `provided_hash` is a valid `[u8]` with size `SHA256_DIGEST_SIZE`.
+ // * libavb gives us a properly-allocated `hash` with size `SHA256_DIGEST_SIZE`.
+ // * the arrays are independent objects and cannot overlap.
+ unsafe { ptr::copy_nonoverlapping(provided_hash.as_ptr(), hash, SHA256_DIGEST_SIZE) };
+
+ Ok(())
+}
+
+/// Wraps a callback to convert the given `IoResult<>` to `None` for libavb.
+///
+/// See corresponding `try_*` function docs.
+unsafe extern "C" fn set_key_version(
+ cert_ops: *mut AvbCertOps,
+ rollback_index_location: usize,
+ key_version: u64,
+) {
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ let result = unsafe { try_set_key_version(cert_ops, rollback_index_location, key_version) };
+
+ // `set_key_version()` is unique in that it has no return value, and therefore cannot fail.
+ // However, our internal C -> Rust logic does have some potential failure points when we unwrap
+ // the C pointers to extract our Rust objects.
+ //
+ // Ignoring the error could be a security risk, as it would silently prevent the device from
+ // updating key rollback versions, so instead we panic here.
+ if let Err(e) = result {
+ panic!("Fatal error in set_key_version(): {:?}", e);
+ }
+}
+
+/// Bounces the C callback into the user-provided Rust implementation.
+///
+/// # Safety
+/// `cert_ops` must have been created via `ScopedAvbOps`.
+unsafe fn try_set_key_version(
+ cert_ops: *mut AvbCertOps,
+ rollback_index_location: usize,
+ key_version: u64,
+) -> IoResult<()> {
+ // SAFETY:
+ // * we only use `cert_ops` objects created via `ScopedAvbOps` as required.
+ // * `cert_ops` is only extracted once and is dropped at the end of the callback.
+ let cert_ops = unsafe { as_cert_ops(cert_ops) }?;
+ cert_ops.set_key_version(rollback_index_location, key_version);
+ Ok(())
+}
+
+/// Wraps a callback to convert the given `IoResult<>` to `None` for libavb.
+///
+/// See corresponding `try_*` function docs.
+unsafe extern "C" fn get_random(
+ cert_ops: *mut AvbCertOps,
+ num_bytes: usize,
+ output: *mut u8,
+) -> AvbIOResult {
+ result_to_io_enum(
+ // SAFETY: see corresponding `try_*` function safety documentation.
+ unsafe { try_get_random(cert_ops, num_bytes, output) },
+ )
+}
+
+/// Bounces the C callback into the user-provided Rust implementation.
+///
+/// # Safety
+/// * `cert_ops` must have been created via `ScopedAvbOps`.
+/// * `output` must point to a valid buffer of size `num_bytes` that we have exclusive access to.
+unsafe fn try_get_random(
+ cert_ops: *mut AvbCertOps,
+ num_bytes: usize,
+ output: *mut u8,
+) -> IoResult<()> {
+ check_nonnull(output)?;
+ if num_bytes == 0 {
+ return Ok(());
+ }
+
+ // SAFETY:
+ // * we've checked that the pointer is non-NULL.
+ // * libavb gives us a properly-allocated `output` with size `num_bytes`.
+ // * we only access the contents via the returned slice.
+ // * the returned slice is not held past the scope of this callback.
+ let output = unsafe { slice::from_raw_parts_mut(output, num_bytes) };
+
+ // SAFETY:
+ // * we only use `cert_ops` objects created via `ScopedAvbOps` as required.
+ // * `cert_ops` is only extracted once and is dropped at the end of the callback.
+ let cert_ops = unsafe { as_cert_ops(cert_ops) }?;
+ cert_ops.get_random(output)
+}
diff --git a/rust/tests/cert_tests.rs b/rust/tests/cert_tests.rs
new file mode 100644
index 0000000..77cd048
--- /dev/null
+++ b/rust/tests/cert_tests.rs
@@ -0,0 +1,289 @@
+// Copyright 2024, 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.
+
+//! libavb_rs certificate tests.
+
+use crate::{
+ build_test_ops_one_image_one_vbmeta,
+ test_data::*,
+ test_ops::{FakeVbmetaKey, TestOps},
+ verify_one_image_one_vbmeta,
+};
+use avb::{
+ cert_generate_unlock_challenge, cert_validate_unlock_credential, CertPermanentAttributes,
+ CertUnlockChallenge, CertUnlockCredential, IoError, SlotVerifyError, CERT_PIK_VERSION_LOCATION,
+ CERT_PSK_VERSION_LOCATION,
+};
+use hex::decode;
+use std::{collections::HashMap, fs, mem::size_of};
+use zerocopy::{AsBytes, FromBytes};
+
+/// Initializes a `TestOps` object such that cert verification will succeed on
+/// `TEST_PARTITION_NAME`.
+///
+/// The returned `TestOps` also contains RNG configured to return the contents of
+/// `TEST_CERT_UNLOCK_CHALLENGE_RNG_PATH`, so that the pre-signed contents of
+/// `TEST_CERT_UNLOCK_CREDENTIAL_PATH` will successfully validate by default.
+fn build_test_cert_ops_one_image_one_vbmeta<'a>() -> TestOps<'a> {
+ let mut ops = build_test_ops_one_image_one_vbmeta();
+
+ // Replace vbmeta with the cert-signed version.
+ ops.add_partition("vbmeta", fs::read(TEST_CERT_VBMETA_PATH).unwrap());
+
+ // Tell `ops` to use cert APIs and to route the default key through cert validation.
+ ops.use_cert = true;
+ ops.default_vbmeta_key = Some(FakeVbmetaKey::Cert);
+
+ // Add the libavb_cert permanent attributes.
+ let perm_attr_bytes = fs::read(TEST_CERT_PERMANENT_ATTRIBUTES_PATH).unwrap();
+ ops.cert_permanent_attributes =
+ Some(CertPermanentAttributes::read_from(&perm_attr_bytes[..]).unwrap());
+ ops.cert_permanent_attributes_hash = Some(
+ decode(TEST_CERT_PERMANENT_ATTRIBUTES_HASH_HEX)
+ .unwrap()
+ .try_into()
+ .unwrap(),
+ );
+
+ // Add the rollbacks for the cert keys.
+ ops.rollbacks
+ .insert(CERT_PIK_VERSION_LOCATION, TEST_CERT_PIK_VERSION);
+ ops.rollbacks
+ .insert(CERT_PSK_VERSION_LOCATION, TEST_CERT_PSK_VERSION);
+
+ // It's non-trivial to sign a challenge without `avbtool.py`, so instead we inject the exact RNG
+ // used by the pre-generated challenge so that we can use the pre-signed credential.
+ ops.cert_fake_rng = fs::read(TEST_CERT_UNLOCK_CHALLENGE_RNG_PATH).unwrap();
+
+ ops
+}
+
+/// Returns the contents of `TEST_CERT_UNLOCK_CREDENTIAL_PATH` as a `CertUnlockCredential`.
+fn test_unlock_credential() -> CertUnlockCredential {
+ let credential_bytes = fs::read(TEST_CERT_UNLOCK_CREDENTIAL_PATH).unwrap();
+ CertUnlockCredential::read_from(&credential_bytes[..]).unwrap()
+}
+
+/// Enough fake RNG data to generate a single unlock challenge.
+const UNLOCK_CHALLENGE_FAKE_RNG: [u8; 16] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
+
+/// Returns a `TestOps` with only enough configuration to generate a single unlock challenge.
+///
+/// The generated unlock challenge will have:
+/// * permanent attributes sourced from the contents of `TEST_CERT_PERMANENT_ATTRIBUTES_PATH`.
+/// * RNG sourced from `UNLOCK_CHALLENGE_FAKE_RNG`.
+fn build_test_cert_ops_unlock_challenge_only<'a>() -> TestOps<'a> {
+ let mut ops = TestOps::default();
+
+ // Permanent attributes are needed for the embedded product ID.
+ let perm_attr_bytes = fs::read(TEST_CERT_PERMANENT_ATTRIBUTES_PATH).unwrap();
+ ops.cert_permanent_attributes =
+ Some(CertPermanentAttributes::read_from(&perm_attr_bytes[..]).unwrap());
+
+ // Fake RNG for unlock challenge generation.
+ ops.cert_fake_rng = UNLOCK_CHALLENGE_FAKE_RNG.into();
+
+ ops
+}
+
+#[test]
+fn cert_verify_succeeds() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+
+ assert!(result.is_ok());
+}
+
+#[test]
+fn cert_verify_sets_key_rollbacks() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+
+ // `cert_key_versions` should start empty and be filled by the `set_key_version()` callback
+ // during cert key validation.
+ assert!(ops.cert_key_versions.is_empty());
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+ assert!(result.is_ok());
+
+ assert_eq!(
+ ops.cert_key_versions,
+ HashMap::from([
+ (CERT_PIK_VERSION_LOCATION, TEST_CERT_PIK_VERSION),
+ (CERT_PSK_VERSION_LOCATION, TEST_CERT_PSK_VERSION)
+ ])
+ );
+}
+
+#[test]
+fn cert_verify_fails_with_pik_rollback_violation() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+ // If the image is signed with a lower key version than our rollback, it should fail to verify.
+ *ops.rollbacks.get_mut(&CERT_PIK_VERSION_LOCATION).unwrap() += 1;
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+
+ assert_eq!(result.unwrap_err(), SlotVerifyError::PublicKeyRejected);
+}
+
+#[test]
+fn cert_verify_fails_with_psk_rollback_violation() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+ // If the image is signed with a lower key version than our rollback, it should fail to verify.
+ *ops.rollbacks.get_mut(&CERT_PSK_VERSION_LOCATION).unwrap() += 1;
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+
+ assert_eq!(result.unwrap_err(), SlotVerifyError::PublicKeyRejected);
+}
+
+#[test]
+fn cert_verify_fails_with_wrong_vbmeta_key() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+ // The default non-cert vbmeta image should fail to verify.
+ ops.add_partition("vbmeta", fs::read(TEST_VBMETA_PATH).unwrap());
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+
+ assert_eq!(result.unwrap_err(), SlotVerifyError::PublicKeyRejected);
+}
+
+#[test]
+fn cert_verify_fails_with_bad_permanent_attributes_hash() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+ // The permanent attributes must match their hash.
+ ops.cert_permanent_attributes_hash.as_mut().unwrap()[0] ^= 0x01;
+
+ let result = verify_one_image_one_vbmeta(&mut ops);
+
+ assert_eq!(result.unwrap_err(), SlotVerifyError::PublicKeyRejected);
+}
+
+#[test]
+fn cert_generate_unlock_challenge_succeeds() {
+ let mut ops = build_test_cert_ops_unlock_challenge_only();
+
+ let challenge = cert_generate_unlock_challenge(&mut ops).unwrap();
+
+ // Make sure the challenge token used our cert callback data correctly.
+ assert_eq!(
+ challenge.product_id_hash,
+ &decode(TEST_CERT_PRODUCT_ID_HASH_HEX).unwrap()[..]
+ );
+ assert_eq!(challenge.challenge, UNLOCK_CHALLENGE_FAKE_RNG);
+}
+
+#[test]
+fn cert_generate_unlock_challenge_fails_without_permanent_attributes() {
+ let mut ops = build_test_cert_ops_unlock_challenge_only();
+
+ // Challenge generation should fail without the product ID provided by the permanent attributes.
+ ops.cert_permanent_attributes = None;
+
+ assert_eq!(
+ cert_generate_unlock_challenge(&mut ops).unwrap_err(),
+ IoError::Io
+ );
+}
+
+#[test]
+fn cert_generate_unlock_challenge_fails_insufficient_rng() {
+ let mut ops = build_test_cert_ops_unlock_challenge_only();
+
+ // Remove a byte of RNG so there isn't enough.
+ ops.cert_fake_rng.pop();
+
+ assert_eq!(
+ cert_generate_unlock_challenge(&mut ops).unwrap_err(),
+ IoError::Io
+ );
+}
+
+#[test]
+fn cert_validate_unlock_credential_success() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+
+ // We don't actually need the challenge here since we've pre-signed it, but we still need to
+ // call this function so the libavb_cert internal state is ready for the unlock cred.
+ let _ = cert_generate_unlock_challenge(&mut ops).unwrap();
+
+ assert_eq!(
+ cert_validate_unlock_credential(&mut ops, &test_unlock_credential()),
+ Ok(true)
+ );
+}
+
+#[test]
+fn cert_validate_unlock_credential_fails_wrong_rng() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+ // Modify the RNG slightly, the cerificate should now fail to validate.
+ ops.cert_fake_rng[0] ^= 0x01;
+
+ let _ = cert_generate_unlock_challenge(&mut ops).unwrap();
+
+ assert_eq!(
+ cert_validate_unlock_credential(&mut ops, &test_unlock_credential()),
+ Ok(false)
+ );
+}
+
+#[test]
+fn cert_validate_unlock_credential_fails_with_pik_rollback_violation() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+ // Rotating the PIK should invalidate all existing unlock keys, which includes our pre-signed
+ // certificate.
+ *ops.rollbacks.get_mut(&CERT_PIK_VERSION_LOCATION).unwrap() += 1;
+
+ let _ = cert_generate_unlock_challenge(&mut ops).unwrap();
+
+ assert_eq!(
+ cert_validate_unlock_credential(&mut ops, &test_unlock_credential()),
+ Ok(false)
+ );
+}
+
+#[test]
+fn cert_validate_unlock_credential_fails_no_challenge() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+
+ // We never called `cert_generate_unlock_challenge()`, so no credentials should validate.
+ assert_eq!(
+ cert_validate_unlock_credential(&mut ops, &test_unlock_credential()),
+ Ok(false)
+ );
+}
+
+// In practice, devices will usually be passing unlock challenges and credentials over fastboot as
+// raw bytes. This test ensures that there are some reasonable APIs available to convert between
+// `CertUnlockChallenge`/`CertUnlockCredential` and byte slices.
+#[test]
+fn cert_validate_unlock_credential_bytes_api() {
+ let mut ops = build_test_cert_ops_one_image_one_vbmeta();
+
+ // Write an unlock challenge to a byte buffer for TX over fastboot.
+ let challenge = cert_generate_unlock_challenge(&mut ops).unwrap();
+ let mut buffer = vec![0u8; size_of::<CertUnlockChallenge>()];
+ assert_eq!(challenge.write_to(&mut buffer[..]), Some(())); // zerocopy::AsBytes.
+
+ // Read an unlock credential from a byte buffer for RX from fastboot.
+ let buffer = vec![0u8; size_of::<CertUnlockCredential>()];
+ let credential = CertUnlockCredential::ref_from(&buffer[..]).unwrap(); // zerocopy::FromBytes.
+
+ // It shouldn't actually validate since the credential is just zeroes, the important thing
+ // is that it compiles.
+ assert_eq!(
+ cert_validate_unlock_credential(&mut ops, credential),
+ Ok(false)
+ );
+}
diff --git a/rust/tests/test_data.rs b/rust/tests/test_data.rs
new file mode 100644
index 0000000..4912bd6
--- /dev/null
+++ b/rust/tests/test_data.rs
@@ -0,0 +1,73 @@
+// Copyright 2024, 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.
+
+//! Test data used in libavb_rs tests.
+//!
+//! These constants must match the values used to create the images in Android.bp.
+
+pub const TEST_IMAGE_PATH: &str = "test_image.img";
+pub const TEST_IMAGE_SIZE: usize = 16 * 1024;
+pub const TEST_IMAGE_SALT_HEX: &str = "1000";
+pub const TEST_HASHTREE_SALT_HEX: &str = "B000";
+pub const TEST_VBMETA_PATH: &str = "test_vbmeta.img";
+pub const TEST_VBMETA_2_PARTITIONS_PATH: &str = "test_vbmeta_2_parts.img";
+pub const TEST_VBMETA_PERSISTENT_DIGEST_PATH: &str = "test_vbmeta_persistent_digest.img";
+pub const TEST_VBMETA_WITH_PROPERTY_PATH: &str = "test_vbmeta_with_property.img";
+pub const TEST_VBMETA_WITH_HASHTREE_PATH: &str = "test_vbmeta_with_hashtree.img";
+pub const TEST_VBMETA_WITH_COMMANDLINE_PATH: &str = "test_vbmeta_with_commandline.img";
+pub const TEST_VBMETA_WITH_CHAINED_PARTITION_PATH: &str = "test_vbmeta_with_chained_partition.img";
+pub const TEST_IMAGE_WITH_VBMETA_FOOTER_PATH: &str = "avbrs_test_image_with_vbmeta_footer.img";
+pub const TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_BOOT_PATH: &str =
+ "avbrs_test_image_with_vbmeta_footer_for_boot.img";
+pub const TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_TEST_PART_2: &str =
+ "avbrs_test_image_with_vbmeta_footer_for_test_part_2.img";
+pub const TEST_PUBLIC_KEY_PATH: &str = "data/testkey_rsa4096_pub.bin";
+pub const TEST_PUBLIC_KEY_RSA8192_PATH: &str = "data/testkey_rsa8192_pub.bin";
+pub const TEST_PARTITION_NAME: &str = "test_part";
+pub const TEST_PARTITION_SLOT_C_NAME: &str = "test_part_c";
+pub const TEST_PARTITION_2_NAME: &str = "test_part_2";
+pub const TEST_PARTITION_PERSISTENT_DIGEST_NAME: &str = "test_part_persistent_digest";
+pub const TEST_PARTITION_HASH_TREE_NAME: &str = "test_part_hashtree";
+pub const TEST_VBMETA_ROLLBACK_LOCATION: usize = 0; // Default value, we don't explicitly set this.
+pub const TEST_PROPERTY_KEY: &str = "test_prop_key";
+pub const TEST_PROPERTY_VALUE: &[u8] = b"test_prop_value";
+pub const TEST_KERNEL_COMMANDLINE: &str = "test_cmdline_key=test_cmdline_value";
+pub const TEST_CHAINED_PARTITION_ROLLBACK_LOCATION: usize = 4;
+pub const TEST_CHAINED_PARTITION_ROLLBACK_INDEX: u64 = 7;
+
+// Expected values determined by examining the vbmeta image with `avbtool info_image`.
+// Images can be found in <out>/soong/.intermediates/external/avb/rust/.
+pub const TEST_IMAGE_DIGEST_HEX: &str =
+ "89e6fd3142917b8c34ac7d30897a907a71bd3bf5d9b39d00bf938b41dcf3b84f";
+pub const TEST_IMAGE_HASH_ALGO: &str = "sha256";
+pub const TEST_HASHTREE_DIGEST_HEX: &str = "5373fc4ee3dd898325eeeffb5a1dbb041900c5f1";
+pub const TEST_HASHTREE_ALGORITHM: &str = "sha1";
+
+// Certificate test data.
+pub const TEST_CERT_PERMANENT_ATTRIBUTES_PATH: &str = "data/cert_permanent_attributes.bin";
+pub const TEST_CERT_VBMETA_PATH: &str = "test_vbmeta_cert.img";
+pub const TEST_CERT_UNLOCK_CHALLENGE_RNG_PATH: &str = "data/cert_unlock_challenge.bin";
+pub const TEST_CERT_UNLOCK_CREDENTIAL_PATH: &str = "data/cert_unlock_credential.bin";
+
+// The cert test keys were both generated with rollback version 42.
+pub const TEST_CERT_PIK_VERSION: u64 = 42;
+pub const TEST_CERT_PSK_VERSION: u64 = 42;
+
+// $ sha256sum external/avb/test/data/cert_permanent_attributes.bin
+pub const TEST_CERT_PERMANENT_ATTRIBUTES_HASH_HEX: &str =
+ "55419e1affff153b58f65ce8a5313a71d2a83a00d0abae10a25b9a8e493d04f7";
+
+// $ sha256sum external/avb/test/data/cert_product_id.bin
+pub const TEST_CERT_PRODUCT_ID_HASH_HEX: &str =
+ "374708fff7719dd5979ec875d56cd2286f6d3cf7ec317a3b25632aab28ec37bb";
diff --git a/rust/tests/test_ops.rs b/rust/tests/test_ops.rs
index d752650..2c25c74 100644
--- a/rust/tests/test_ops.rs
+++ b/rust/tests/test_ops.rs
@@ -14,7 +14,10 @@
//! Provides `avb::Ops` test fixtures.
-use avb::{IoError, IoResult, Ops, PublicKeyForPartitionInfo};
+use avb::{
+ cert_validate_vbmeta_public_key, CertOps, CertPermanentAttributes, IoError, IoResult, Ops,
+ PublicKeyForPartitionInfo, SHA256_DIGEST_SIZE,
+};
use std::{cmp::min, collections::HashMap, ffi::CStr};
#[cfg(feature = "uuid")]
use uuid::Uuid;
@@ -66,14 +69,18 @@ impl<'a> FakePartition<'a> {
}
}
-/// Fake vbmeta key state.
-pub struct FakeVbmetaKeyState {
- /// Key trust & rollback index info.
- pub info: PublicKeyForPartitionInfo,
-
- /// If specified, indicates the specific partition this vbmeta is tied to (for
- /// `validate_public_key_for_partition()`).
- pub for_partition: Option<&'static str>,
+/// Fake vbmeta key.
+pub enum FakeVbmetaKey {
+ /// Standard AVB validation using a hardcoded key; if the signing key matches these contents
+ /// it is accepted, otherwise it's rejected.
+ Avb {
+ /// Expected public key contents.
+ public_key: Vec<u8>,
+ /// Expected public key metadata contents.
+ public_key_metadata: Option<Vec<u8>>,
+ },
+ /// libavb_cert validation using the permanent attributes.
+ Cert,
}
/// Fake `Ops` test fixture.
@@ -84,11 +91,15 @@ pub struct TestOps<'a> {
/// Partitions to provide to libavb callbacks.
pub partitions: HashMap<&'static str, FakePartition<'a>>,
- /// Vbmeta public keys as a map of {(key, metadata): state}. Querying unknown keys will
- /// return `IoError::Io`.
+ /// Default vbmeta key to use for the `validate_vbmeta_public_key()` callback, or `None` to
+ /// return `IoError::Io` when accessing this key.
+ pub default_vbmeta_key: Option<FakeVbmetaKey>,
+
+ /// Additional vbmeta keys for the `validate_public_key_for_partition()` callback.
///
- /// See `add_vbmeta_key*()` functions for simpler wrappers to inject these keys.
- pub vbmeta_keys: HashMap<(Vec<u8>, Option<Vec<u8>>), FakeVbmetaKeyState>,
+ /// Stored as a map of {partition_name: (key, rollback_location)}. Querying keys for partitions
+ /// not in this map will return `IoError::Io`.
+ pub vbmeta_keys_for_partition: HashMap<&'static str, (FakeVbmetaKey, u32)>,
/// Rollback indices. Accessing unknown locations will return `IoError::Io`.
pub rollbacks: HashMap<usize, u64>,
@@ -100,6 +111,21 @@ pub struct TestOps<'a> {
/// a non-existent persistent value will create it; to simulate `NoSuchValue` instead,
/// create an entry with `Err(IoError::NoSuchValue)` as the value.
pub persistent_values: HashMap<String, IoResult<Vec<u8>>>,
+
+ /// Set to true to enable `CertOps`; defaults to false.
+ pub use_cert: bool,
+
+ /// Cert permanent attributes, or `None` to trigger `IoError` on access.
+ pub cert_permanent_attributes: Option<CertPermanentAttributes>,
+
+ /// Cert permament attributes hash, or `None` to trigger `IoError` on access.
+ pub cert_permanent_attributes_hash: Option<[u8; SHA256_DIGEST_SIZE]>,
+
+ /// Cert key versions; will be updated by the `set_key_version()` cert callback.
+ pub cert_key_versions: HashMap<usize, u64>,
+
+ /// Fake RNG values to provide, or `IoError` if there aren't enough.
+ pub cert_fake_rng: Vec<u8>,
}
impl<'a> TestOps<'a> {
@@ -153,41 +179,33 @@ impl<'a> TestOps<'a> {
.insert(name.into(), contents.map(|b| b.into()));
}
- /// Adds a fake vbmeta key not tied to any partition.
- pub fn add_vbmeta_key(&mut self, key: Vec<u8>, metadata: Option<Vec<u8>>, trusted: bool) {
- self.vbmeta_keys.insert(
- (key, metadata),
- FakeVbmetaKeyState {
- // `rollback_index_location` doesn't matter in this case, it will be read from
- // the vbmeta blob.
- info: PublicKeyForPartitionInfo {
- trusted,
- rollback_index_location: 0,
- },
- for_partition: None,
- },
- );
- }
-
- /// Adds a fake vbmeta key tied to the given partition and rollback index location.
- pub fn add_vbmeta_key_for_partition(
+ /// Internal helper to validate a vbmeta key.
+ fn validate_fake_key(
&mut self,
- key: Vec<u8>,
- metadata: Option<Vec<u8>>,
- trusted: bool,
- partition: &'static str,
- rollback_index_location: u32,
- ) {
- self.vbmeta_keys.insert(
- (key, metadata),
- FakeVbmetaKeyState {
- info: PublicKeyForPartitionInfo {
- trusted,
- rollback_index_location,
- },
- for_partition: Some(partition),
- },
- );
+ partition: Option<&str>,
+ public_key: &[u8],
+ public_key_metadata: Option<&[u8]>,
+ ) -> IoResult<bool> {
+ let fake_key = match partition {
+ None => self.default_vbmeta_key.as_ref(),
+ Some(p) => self.vbmeta_keys_for_partition.get(p).map(|(key, _)| key),
+ }
+ .ok_or(IoError::Io)?;
+
+ match fake_key {
+ FakeVbmetaKey::Avb {
+ public_key: expected_key,
+ public_key_metadata: expected_metadata,
+ } => {
+ // avb: only accept if it matches the hardcoded key + metadata.
+ Ok(expected_key == public_key
+ && expected_metadata.as_deref() == public_key_metadata)
+ }
+ FakeVbmetaKey::Cert => {
+ // avb_cert: forward to the cert helper function.
+ cert_validate_vbmeta_public_key(self, public_key, public_key_metadata)
+ }
+ }
}
}
@@ -195,10 +213,16 @@ impl Default for TestOps<'_> {
fn default() -> Self {
Self {
partitions: HashMap::new(),
- vbmeta_keys: HashMap::new(),
+ default_vbmeta_key: None,
+ vbmeta_keys_for_partition: HashMap::new(),
rollbacks: HashMap::new(),
unlock_state: Err(IoError::Io),
persistent_values: HashMap::new(),
+ use_cert: false,
+ cert_permanent_attributes: None,
+ cert_permanent_attributes_hash: None,
+ cert_key_versions: HashMap::new(),
+ cert_fake_rng: Vec::new(),
}
}
}
@@ -259,13 +283,7 @@ impl<'a> Ops<'a> for TestOps<'a> {
public_key: &[u8],
public_key_metadata: Option<&[u8]>,
) -> IoResult<bool> {
- self.vbmeta_keys
- // The compiler can't match (&[u8], Option<&[u8]>) to keys of type
- // (Vec<u8>, Option<Vec<u8>>) so we turn the &[u8] into vectors here. This is a bit
- // inefficient, but it's simple which is more important for tests than efficiency.
- .get(&(public_key.to_vec(), public_key_metadata.map(|m| m.to_vec())))
- .ok_or(IoError::Io)
- .map(|k| k.info.trusted)
+ self.validate_fake_key(None, public_key, public_key_metadata)
}
fn read_rollback_index(&mut self, location: usize) -> IoResult<u64> {
@@ -345,19 +363,54 @@ impl<'a> Ops<'a> for TestOps<'a> {
public_key: &[u8],
public_key_metadata: Option<&[u8]>,
) -> IoResult<PublicKeyForPartitionInfo> {
- let key = self
- .vbmeta_keys
- .get(&(public_key.to_vec(), public_key_metadata.map(|m| m.to_vec())))
- .ok_or(IoError::Io)?;
-
- if let Some(for_partition) = key.for_partition {
- if for_partition == partition.to_str()? {
- // The key is registered for this partition; return its info.
- return Ok(key.info);
- }
+ let partition = partition.to_str()?;
+
+ let rollback_index_location = self
+ .vbmeta_keys_for_partition
+ .get(partition)
+ .ok_or(IoError::Io)?
+ .1;
+
+ Ok(PublicKeyForPartitionInfo {
+ trusted: self.validate_fake_key(Some(partition), public_key, public_key_metadata)?,
+ rollback_index_location,
+ })
+ }
+
+ fn cert_ops(&mut self) -> Option<&mut dyn CertOps> {
+ match self.use_cert {
+ true => Some(self),
+ false => None,
}
+ }
+}
- // No match.
- Err(IoError::Io)
+impl<'a> CertOps for TestOps<'a> {
+ fn read_permanent_attributes(
+ &mut self,
+ attributes: &mut CertPermanentAttributes,
+ ) -> IoResult<()> {
+ *attributes = self.cert_permanent_attributes.ok_or(IoError::Io)?;
+ Ok(())
+ }
+
+ fn read_permanent_attributes_hash(&mut self) -> IoResult<[u8; SHA256_DIGEST_SIZE]> {
+ self.cert_permanent_attributes_hash.ok_or(IoError::Io)
+ }
+
+ fn set_key_version(&mut self, rollback_index_location: usize, key_version: u64) {
+ self.cert_key_versions
+ .insert(rollback_index_location, key_version);
+ }
+
+ fn get_random(&mut self, bytes: &mut [u8]) -> IoResult<()> {
+ if bytes.len() > self.cert_fake_rng.len() {
+ return Err(IoError::Io);
+ }
+
+ let leftover = self.cert_fake_rng.split_off(bytes.len());
+ bytes.copy_from_slice(&self.cert_fake_rng[..]);
+ self.cert_fake_rng = leftover;
+ Ok(())
}
}
diff --git a/rust/tests/tests.rs b/rust/tests/tests.rs
index 0e9b3e2..d747ac3 100644
--- a/rust/tests/tests.rs
+++ b/rust/tests/tests.rs
@@ -12,5 +12,42 @@
// See the License for the specific language governing permissions and
// limitations under the License.
+mod cert_tests;
+mod test_data;
mod test_ops;
mod verify_tests;
+
+use avb::{slot_verify, HashtreeErrorMode, SlotVerifyData, SlotVerifyFlags, SlotVerifyResult};
+use std::{ffi::CString, fs};
+use test_data::*;
+use test_ops::{FakeVbmetaKey, TestOps};
+
+/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME`.
+///
+/// This usually forms the basis of the `TestOps` objects used, with tests modifying the returned
+/// object as needed for the individual test case.
+fn build_test_ops_one_image_one_vbmeta<'a>() -> TestOps<'a> {
+ let mut ops = TestOps::default();
+ ops.add_partition(TEST_PARTITION_NAME, fs::read(TEST_IMAGE_PATH).unwrap());
+ ops.add_partition("vbmeta", fs::read(TEST_VBMETA_PATH).unwrap());
+ ops.default_vbmeta_key = Some(FakeVbmetaKey::Avb {
+ public_key: fs::read(TEST_PUBLIC_KEY_PATH).unwrap(),
+ public_key_metadata: None,
+ });
+ ops.rollbacks.insert(TEST_VBMETA_ROLLBACK_LOCATION, 0);
+ ops.unlock_state = Ok(false);
+ ops
+}
+
+/// Calls `slot_verify()` using standard args for `build_test_ops_one_image_one_vbmeta()` setup.
+fn verify_one_image_one_vbmeta<'a>(
+ ops: &mut TestOps<'a>,
+) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
+ slot_verify(
+ ops,
+ &[&CString::new(TEST_PARTITION_NAME).unwrap()],
+ None,
+ SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
+ HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO,
+ )
+}
diff --git a/rust/tests/verify_tests.rs b/rust/tests/verify_tests.rs
index 2e4aaad..dfe4ec8 100644
--- a/rust/tests/verify_tests.rs
+++ b/rust/tests/verify_tests.rs
@@ -14,7 +14,12 @@
//! libavb_rs verification tests.
-use crate::test_ops::TestOps;
+use crate::{
+ build_test_ops_one_image_one_vbmeta,
+ test_data::*,
+ test_ops::{FakeVbmetaKey, TestOps},
+ verify_one_image_one_vbmeta,
+};
use avb::{
slot_verify, ChainPartitionDescriptor, ChainPartitionDescriptorFlags, Descriptor,
HashDescriptor, HashDescriptorFlags, HashtreeDescriptor, HashtreeDescriptorFlags,
@@ -26,73 +31,10 @@ use std::{ffi::CString, fs};
#[cfg(feature = "uuid")]
use uuid::uuid;
-// These constants must match the values used to create the images in Android.bp.
-const TEST_IMAGE_PATH: &str = "test_image.img";
-const TEST_IMAGE_SIZE: usize = 16 * 1024;
-const TEST_IMAGE_SALT_HEX: &str = "1000";
-const TEST_HASHTREE_SALT_HEX: &str = "B000";
-const TEST_VBMETA_PATH: &str = "test_vbmeta.img";
-const TEST_VBMETA_2_PARTITIONS_PATH: &str = "test_vbmeta_2_parts.img";
-const TEST_VBMETA_PERSISTENT_DIGEST_PATH: &str = "test_vbmeta_persistent_digest.img";
-const TEST_VBMETA_WITH_PROPERTY_PATH: &str = "test_vbmeta_with_property.img";
-const TEST_VBMETA_WITH_HASHTREE_PATH: &str = "test_vbmeta_with_hashtree.img";
-const TEST_VBMETA_WITH_COMMANDLINE_PATH: &str = "test_vbmeta_with_commandline.img";
-const TEST_VBMETA_WITH_CHAINED_PARTITION_PATH: &str = "test_vbmeta_with_chained_partition.img";
-const TEST_IMAGE_WITH_VBMETA_FOOTER_PATH: &str = "avbrs_test_image_with_vbmeta_footer.img";
-const TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_BOOT_PATH: &str =
- "avbrs_test_image_with_vbmeta_footer_for_boot.img";
-const TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_TEST_PART_2: &str =
- "avbrs_test_image_with_vbmeta_footer_for_test_part_2.img";
-const TEST_PUBLIC_KEY_PATH: &str = "data/testkey_rsa4096_pub.bin";
-const TEST_PUBLIC_KEY_RSA8192_PATH: &str = "data/testkey_rsa8192_pub.bin";
-const TEST_PARTITION_NAME: &str = "test_part";
-const TEST_PARTITION_SLOT_C_NAME: &str = "test_part_c";
-const TEST_PARTITION_2_NAME: &str = "test_part_2";
-const TEST_PARTITION_PERSISTENT_DIGEST_NAME: &str = "test_part_persistent_digest";
-const TEST_PARTITION_HASH_TREE_NAME: &str = "test_part_hashtree";
-const TEST_VBMETA_ROLLBACK_LOCATION: usize = 0; // Default value, we don't explicitly set this.
-const TEST_PROPERTY_KEY: &str = "test_prop_key";
-const TEST_PROPERTY_VALUE: &[u8] = b"test_prop_value";
-const TEST_KERNEL_COMMANDLINE: &str = "test_cmdline_key=test_cmdline_value";
-const TEST_CHAINED_PARTITION_ROLLBACK_LOCATION: usize = 4;
-const TEST_CHAINED_PARTITION_ROLLBACK_INDEX: u64 = 7;
-
-// Expected values determined by examining the vbmeta image with `avbtool info_image`.
-// Images can be found in <out>/soong/.intermediates/external/avb/rust/.
-const TEST_IMAGE_DIGEST_HEX: &str =
- "89e6fd3142917b8c34ac7d30897a907a71bd3bf5d9b39d00bf938b41dcf3b84f";
-const TEST_IMAGE_HASH_ALGO: &str = "sha256";
-const TEST_HASHTREE_DIGEST_HEX: &str = "5373fc4ee3dd898325eeeffb5a1dbb041900c5f1";
-const TEST_HASHTREE_ALGORITHM: &str = "sha1";
-
-/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME`.
-fn test_ops_one_image_one_vbmeta<'a>() -> TestOps<'a> {
- let mut ops = TestOps::default();
- ops.add_partition(TEST_PARTITION_NAME, fs::read(TEST_IMAGE_PATH).unwrap());
- ops.add_partition("vbmeta", fs::read(TEST_VBMETA_PATH).unwrap());
- ops.add_vbmeta_key(fs::read(TEST_PUBLIC_KEY_PATH).unwrap(), None, true);
- ops.rollbacks.insert(TEST_VBMETA_ROLLBACK_LOCATION, 0);
- ops.unlock_state = Ok(false);
- ops
-}
-
-/// Calls `slot_verify()` using standard args for `test_ops_one_image_one_vbmeta()` setup.
-fn verify_one_image_one_vbmeta<'a>(
- ops: &mut TestOps<'a>,
-) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
- slot_verify(
- ops,
- &[&CString::new(TEST_PARTITION_NAME).unwrap()],
- None,
- SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE,
- HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO,
- )
-}
-
/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME` and
/// `TEST_PARTITION_2_NAME`.
-fn test_ops_two_images_one_vbmeta<'a>() -> TestOps<'a> {
- let mut ops = test_ops_one_image_one_vbmeta();
+fn build_test_ops_two_images_one_vbmeta<'a>() -> TestOps<'a> {
+ let mut ops = build_test_ops_one_image_one_vbmeta();
// Add in the contents of the second partition and overwrite the vbmeta partition to
// include both partition descriptors.
ops.add_partition(TEST_PARTITION_2_NAME, fs::read(TEST_IMAGE_PATH).unwrap());
@@ -116,8 +58,8 @@ fn verify_two_images<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, SlotVeri
/// Initializes a `TestOps` object such that verification will succeed on the `boot` partition with
/// a combined image + vbmeta.
-fn test_ops_boot_partition<'a>() -> TestOps<'a> {
- let mut ops = test_ops_one_image_one_vbmeta();
+fn build_test_ops_boot_partition<'a>() -> TestOps<'a> {
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.partitions.clear();
ops.add_partition(
"boot",
@@ -126,7 +68,7 @@ fn test_ops_boot_partition<'a>() -> TestOps<'a> {
ops
}
-/// Calls `slot_verify()` using standard args for `test_ops_boot_partition()` setup.
+/// Calls `slot_verify()` using standard args for `build_test_ops_boot_partition()` setup.
fn verify_boot_partition<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
slot_verify(
ops,
@@ -142,8 +84,8 @@ fn verify_boot_partition<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, Slot
/// Initializes a `TestOps` object such that verification will succeed on
/// `TEST_PARTITION_PERSISTENT_DIGEST_NAME`.
-fn test_ops_persistent_digest<'a>(image: Vec<u8>) -> TestOps<'a> {
- let mut ops = test_ops_one_image_one_vbmeta();
+fn build_test_ops_persistent_digest<'a>(image: Vec<u8>) -> TestOps<'a> {
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.partitions.clear();
// Use the vbmeta image with the persistent digest descriptor.
ops.add_partition(
@@ -155,7 +97,7 @@ fn test_ops_persistent_digest<'a>(image: Vec<u8>) -> TestOps<'a> {
ops
}
-/// Calls `slot_verify()` using standard args for `test_ops_persistent_digest()` setup.
+/// Calls `slot_verify()` using standard args for `build_test_ops_persistent_digest()` setup.
fn verify_persistent_digest<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, SlotVerifyData<'a>> {
slot_verify(
ops,
@@ -184,7 +126,7 @@ fn persistent_digest_value_name() -> String {
#[test]
fn one_image_one_vbmeta_passes_verification_with_correct_data() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -225,7 +167,7 @@ fn one_image_one_vbmeta_passes_verification_with_correct_data() {
#[test]
fn preloaded_image_passes_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
// Use preloaded data instead for the test partition.
let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
@@ -243,7 +185,7 @@ fn preloaded_image_passes_verification() {
#[test]
fn verification_data_from_disk_can_outlive_ops() {
let result = {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
verify_one_image_one_vbmeta(&mut ops)
};
@@ -264,7 +206,7 @@ fn verification_data_preloaded_can_outlive_ops() {
let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
let result = {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
verify_one_image_one_vbmeta(&mut ops)
};
@@ -287,7 +229,7 @@ fn verification_data_preloaded_can_outlive_ops() {
// fn verification_data_preloaded_cannot_outlive_result() {
// let result = {
// let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
-// let mut ops = test_ops_one_image_one_vbmeta();
+// let mut ops = build_test_ops_one_image_one_vbmeta();
// ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
// verify_one_image_one_vbmeta(&mut ops)
// };
@@ -296,7 +238,7 @@ fn verification_data_preloaded_can_outlive_ops() {
#[test]
fn slotted_partition_passes_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
// Move the partitions to a "_c" slot.
ops.partitions.clear();
ops.add_partition(
@@ -319,7 +261,7 @@ fn slotted_partition_passes_verification() {
#[test]
fn two_images_one_vbmeta_passes_verification() {
- let mut ops = test_ops_two_images_one_vbmeta();
+ let mut ops = build_test_ops_two_images_one_vbmeta();
let result = verify_two_images(&mut ops);
@@ -340,20 +282,25 @@ fn two_images_one_vbmeta_passes_verification() {
#[test]
fn combined_image_vbmeta_partition_passes_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.partitions.clear();
// Register the single combined image + vbmeta in `TEST_PARTITION_NAME`.
ops.add_partition(
TEST_PARTITION_NAME,
fs::read(TEST_IMAGE_WITH_VBMETA_FOOTER_PATH).unwrap(),
);
- // For a combined image we need to register the public key specifically for this partition.
- ops.add_vbmeta_key_for_partition(
- fs::read(TEST_PUBLIC_KEY_PATH).unwrap(),
- None,
- true,
+ // For a combined image it should not attempt to use the default "vbmeta" key, instead we
+ // register the public key specifically for this partition.
+ ops.default_vbmeta_key = None;
+ ops.vbmeta_keys_for_partition.insert(
TEST_PARTITION_NAME,
- TEST_VBMETA_ROLLBACK_LOCATION as u32,
+ (
+ FakeVbmetaKey::Avb {
+ public_key: fs::read(TEST_PUBLIC_KEY_PATH).unwrap(),
+ public_key_metadata: None,
+ },
+ TEST_VBMETA_ROLLBACK_LOCATION as u32,
+ ),
);
let result = slot_verify(
@@ -389,7 +336,7 @@ fn combined_image_vbmeta_partition_passes_verification() {
// Validate the custom behavior if the combined image + vbmeta live in the `boot` partition.
#[test]
fn vbmeta_with_boot_partition_passes_verification() {
- let mut ops = test_ops_boot_partition();
+ let mut ops = build_test_ops_boot_partition();
let result = verify_boot_partition(&mut ops);
@@ -413,7 +360,7 @@ fn persistent_digest_verification_updates_persistent_value() {
// calculated on-demand and stored into a named persistent value. So our test image can contain
// anything, but does have to match the size indicated by the descriptor.
let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE];
- let mut ops = test_ops_persistent_digest(image_contents.clone());
+ let mut ops = build_test_ops_persistent_digest(image_contents.clone());
{
let result = verify_persistent_digest(&mut ops);
@@ -429,7 +376,7 @@ fn persistent_digest_verification_updates_persistent_value() {
#[cfg(feature = "uuid")]
#[test]
fn successful_verification_substitutes_partition_guid() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.partitions.get_mut("vbmeta").unwrap().uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef");
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -445,7 +392,7 @@ fn successful_verification_substitutes_partition_guid() {
#[cfg(feature = "uuid")]
#[test]
fn successful_verification_substitutes_boot_partition_guid() {
- let mut ops = test_ops_boot_partition();
+ let mut ops = build_test_ops_boot_partition();
ops.partitions.get_mut("boot").unwrap().uuid = uuid!("01234567-89ab-cdef-0123-456789abcdef");
let result = verify_boot_partition(&mut ops);
@@ -461,7 +408,7 @@ fn successful_verification_substitutes_boot_partition_guid() {
#[test]
fn corrupted_image_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
modify_partition_contents(&mut ops, TEST_PARTITION_NAME);
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -472,7 +419,7 @@ fn corrupted_image_fails_verification() {
#[test]
fn read_partition_callback_error_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.partitions.remove(TEST_PARTITION_NAME);
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -483,7 +430,7 @@ fn read_partition_callback_error_fails_verification() {
#[test]
fn undersized_partition_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.partitions
.get_mut(TEST_PARTITION_NAME)
.unwrap()
@@ -499,7 +446,7 @@ fn undersized_partition_fails_verification() {
#[test]
fn corrupted_vbmeta_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
modify_partition_contents(&mut ops, "vbmeta");
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -510,7 +457,7 @@ fn corrupted_vbmeta_fails_verification() {
#[test]
fn rollback_violation_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
// Device with rollback = 1 should refuse to boot image with rollback = 0.
ops.rollbacks.insert(TEST_VBMETA_ROLLBACK_LOCATION, 1);
@@ -522,7 +469,7 @@ fn rollback_violation_fails_verification() {
#[test]
fn rollback_callback_error_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.rollbacks.clear();
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -533,8 +480,11 @@ fn rollback_callback_error_fails_verification() {
#[test]
fn untrusted_vbmeta_keys_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
- ops.add_vbmeta_key(fs::read(TEST_PUBLIC_KEY_PATH).unwrap(), None, false);
+ let mut ops = build_test_ops_one_image_one_vbmeta();
+ ops.default_vbmeta_key = Some(FakeVbmetaKey::Avb {
+ public_key: b"not_the_key".into(),
+ public_key_metadata: None,
+ });
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -544,8 +494,8 @@ fn untrusted_vbmeta_keys_fails_verification() {
#[test]
fn vbmeta_keys_callback_error_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
- ops.vbmeta_keys.clear();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
+ ops.default_vbmeta_key = None;
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -555,7 +505,7 @@ fn vbmeta_keys_callback_error_fails_verification() {
#[test]
fn unlock_state_callback_error_fails_verification() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
ops.unlock_state = Err(IoError::Io);
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -567,7 +517,7 @@ fn unlock_state_callback_error_fails_verification() {
#[test]
fn persistent_digest_mismatch_fails_verification() {
let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE];
- let mut ops = test_ops_persistent_digest(image_contents.clone());
+ let mut ops = build_test_ops_persistent_digest(image_contents.clone());
// Put in an incorrect persistent digest; `slot_verify()` should detect the mismatch and fail.
ops.add_persistent_value(&persistent_digest_value_name(), Ok(b"incorrect_digest"));
// Make a copy so we can verify the persistent values don't change on failure.
@@ -582,7 +532,7 @@ fn persistent_digest_mismatch_fails_verification() {
#[test]
fn persistent_digest_callback_error_fails_verification() {
let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE];
- let mut ops = test_ops_persistent_digest(image_contents.clone());
+ let mut ops = build_test_ops_persistent_digest(image_contents.clone());
ops.add_persistent_value(&persistent_digest_value_name(), Err(IoError::NoSuchValue));
let result = verify_persistent_digest(&mut ops);
@@ -593,7 +543,7 @@ fn persistent_digest_callback_error_fails_verification() {
#[test]
fn corrupted_image_with_allow_verification_error_flag_fails_verification_with_data() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
modify_partition_contents(&mut ops, TEST_PARTITION_NAME);
let result = slot_verify(
@@ -626,7 +576,7 @@ fn corrupted_image_with_allow_verification_error_flag_fails_verification_with_da
#[test]
fn one_image_one_vbmeta_verification_data_display() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -639,7 +589,7 @@ fn one_image_one_vbmeta_verification_data_display() {
#[test]
fn preloaded_image_verification_data_display() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
let preloaded = fs::read(TEST_IMAGE_PATH).unwrap();
ops.add_preloaded_partition(TEST_PARTITION_NAME, &preloaded);
@@ -654,7 +604,7 @@ fn preloaded_image_verification_data_display() {
#[test]
fn two_images_one_vbmeta_verification_data_display() {
- let mut ops = test_ops_two_images_one_vbmeta();
+ let mut ops = build_test_ops_two_images_one_vbmeta();
let result = verify_two_images(&mut ops);
@@ -667,7 +617,7 @@ fn two_images_one_vbmeta_verification_data_display() {
#[test]
fn corrupted_image_verification_data_display() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
modify_partition_contents(&mut ops, TEST_PARTITION_NAME);
let result = slot_verify(
@@ -691,7 +641,7 @@ fn corrupted_image_verification_data_display() {
#[test]
fn one_image_gives_single_descriptor() {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
let result = verify_one_image_one_vbmeta(&mut ops);
@@ -701,7 +651,7 @@ fn one_image_gives_single_descriptor() {
#[test]
fn two_images_gives_two_descriptors() {
- let mut ops = test_ops_two_images_one_vbmeta();
+ let mut ops = build_test_ops_two_images_one_vbmeta();
let result = verify_two_images(&mut ops);
@@ -718,7 +668,7 @@ fn two_images_gives_two_descriptors() {
/// 3. run verification
/// 4. check that the given `descriptor` exists in the verification data
fn verify_and_find_descriptor(vbmeta_path: &str, expected_descriptor: &Descriptor) {
- let mut ops = test_ops_one_image_one_vbmeta();
+ let mut ops = build_test_ops_one_image_one_vbmeta();
// Replace the vbmeta image with the requested variation.
ops.add_partition("vbmeta", fs::read(vbmeta_path).unwrap());
@@ -793,7 +743,7 @@ fn verify_kernel_commandline_descriptor() {
#[test]
fn verify_chain_partition_descriptor() {
- let mut ops = test_ops_two_images_one_vbmeta();
+ let mut ops = build_test_ops_two_images_one_vbmeta();
// Set up the fake ops to contain:
// * the default test image in TEST_PARTITION_NAME