diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-30 23:02:19 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-04-30 23:02:19 +0000 |
commit | 605ae13dda96ebdfb651956a9c50f8ae3ef38eb8 (patch) | |
tree | b6725d91c88dd8c52e9efa011d743a62d1c093dd | |
parent | 0e138ecf18a33795a3fb84dbf1115ee5397a3f15 (diff) | |
parent | f9d4620d48b2a2b7310bbb56c0c8af821964c5f1 (diff) | |
download | avb-sdk-release.tar.gz |
Snap for 11785460 from f9d4620d48b2a2b7310bbb56c0c8af821964c5f1 to sdk-releasesdk-release
Change-Id: I39719f58c8dc2721dda43161900cc961c81c8af7
-rw-r--r-- | rust/Android.bp | 38 | ||||
-rw-r--r-- | rust/src/cert.rs | 177 | ||||
-rw-r--r-- | rust/src/error.rs | 41 | ||||
-rw-r--r-- | rust/src/lib.rs | 6 | ||||
-rw-r--r-- | rust/src/ops.rs | 188 | ||||
-rw-r--r-- | rust/tests/cert_tests.rs | 289 | ||||
-rw-r--r-- | rust/tests/test_data.rs | 73 | ||||
-rw-r--r-- | rust/tests/test_ops.rs | 187 | ||||
-rw-r--r-- | rust/tests/tests.rs | 37 | ||||
-rw-r--r-- | rust/tests/verify_tests.rs | 174 |
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 |