diff options
author | David Pursell <dpursell@google.com> | 2024-03-29 11:35:35 -0700 |
---|---|---|
committer | David Pursell <dpursell@google.com> | 2024-04-24 10:38:18 -0700 |
commit | e57ea84d3f913230345b3a0090f160f878d6ef9c (patch) | |
tree | 8daec718317b5fb5d9968ec9e9cacc90a2caf357 | |
parent | 87b736478963f449fa00b187f2be0ba384d3a943 (diff) | |
download | avb-e57ea84d3f913230345b3a0090f160f878d6ef9c.tar.gz |
libavb_rs: avb_cert key validation
Implements the callbacks and API required for key validation and
testing.
Authenticated unlock APIs are still unimplemented for the time being.
Bug: b/320543206
Test: atest libavb_rs_test libavb_rs_uuid_test libavb_rs_unittest libavb_rs_uuid_unittest
Change-Id: Id9b999706de476f0da75885af239decd59381a95
-rw-r--r-- | rust/Android.bp | 32 | ||||
-rw-r--r-- | rust/src/cert.rs | 43 | ||||
-rw-r--r-- | rust/src/error.rs | 41 | ||||
-rw-r--r-- | rust/src/lib.rs | 6 | ||||
-rw-r--r-- | rust/src/ops.rs | 145 | ||||
-rw-r--r-- | rust/tests/cert_tests.rs | 132 | ||||
-rw-r--r-- | rust/tests/test_data.rs | 12 | ||||
-rw-r--r-- | rust/tests/test_ops.rs | 68 | ||||
-rw-r--r-- | rust/tests/tests.rs | 1 |
9 files changed, 441 insertions, 39 deletions
diff --git a/rust/Android.bp b/rust/Android.bp index c390553..756813d 100644 --- a/rust/Android.bp +++ b/rust/Android.bp @@ -35,6 +35,7 @@ rust_defaults { "--default-enum-style rust", "--with-derive-default", "--with-derive-custom=Avb.*Descriptor=FromZeroes,FromBytes", + "--with-derive-custom=AvbCertPermanentAttributes=FromZeroes,FromBytes", "--allowlist-type=AvbDescriptorTag", "--allowlist-type=Avb.*Flags", "--allowlist-function=.*", @@ -52,7 +53,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 +64,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 +162,7 @@ rust_defaults { "libzerocopy_nostd_noalloc", ], whole_static_libs: [ - "libavb_baremetal", + "libavb_cert_baremetal", ], stdlibs: [ "libcore.rust_sysroot", @@ -178,7 +179,7 @@ rust_defaults { "libzerocopy", ], whole_static_libs: [ - "libavb", + "libavb_cert", ], } @@ -246,7 +247,7 @@ rust_library { "libavb_rs", ], whole_static_libs: [ - "libavb", + "libavb_cert", ], } @@ -290,6 +291,7 @@ rust_defaults { name: "libavb_rs_test.defaults", srcs: ["tests/tests.rs"], data: [ + ":avb_cert_test_permanent_attributes", ":avb_testkey_rsa4096_pub_bin", ":avb_testkey_rsa8192_pub_bin", ":avbrs_test_image", @@ -298,13 +300,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 +396,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..cc43367 100644 --- a/rust/src/cert.rs +++ b/rust/src/cert.rs @@ -83,7 +83,9 @@ //! # 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}; +use avb_bindgen::avb_cert_validate_vbmeta_public_key; +use core::pin::pin; /// libavb_cert permanent attributes. pub use avb_bindgen::AvbCertPermanentAttributes as CertPermanentAttributes; @@ -203,14 +205,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. 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..cb8b6d3 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, 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..5263471 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: None, // TODO(b/320543206): implement. }, 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,114 @@ 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(()) +} diff --git a/rust/tests/cert_tests.rs b/rust/tests/cert_tests.rs new file mode 100644 index 0000000..e18dc83 --- /dev/null +++ b/rust/tests/cert_tests.rs @@ -0,0 +1,132 @@ +// 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::{ + CertPermanentAttributes, SlotVerifyError, CERT_PIK_VERSION_LOCATION, CERT_PSK_VERSION_LOCATION, +}; +use hex::decode; +use std::{collections::HashMap, fs}; +use zerocopy::FromBytes; + +/// Initializes a `TestOps` object such that cert verification will succeed on `TEST_PARTITION_NAME`. +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); + + 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); +} diff --git a/rust/tests/test_data.rs b/rust/tests/test_data.rs index f7fc499..683b869 100644 --- a/rust/tests/test_data.rs +++ b/rust/tests/test_data.rs @@ -53,3 +53,15 @@ pub const TEST_IMAGE_DIGEST_HEX: &str = 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"; + +// 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"; diff --git a/rust/tests/test_ops.rs b/rust/tests/test_ops.rs index 526bf4d..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; @@ -76,6 +79,8 @@ pub enum FakeVbmetaKey { /// Expected public key metadata contents. public_key_metadata: Option<Vec<u8>>, }, + /// libavb_cert validation using the permanent attributes. + Cert, } /// Fake `Ops` test fixture. @@ -106,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> { @@ -181,6 +201,10 @@ impl<'a> TestOps<'a> { 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) + } } } } @@ -194,6 +218,11 @@ impl Default for TestOps<'_> { 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(), } } } @@ -347,4 +376,41 @@ impl<'a> Ops<'a> for TestOps<'a> { rollback_index_location, }) } + + fn cert_ops(&mut self) -> Option<&mut dyn CertOps> { + match self.use_cert { + true => Some(self), + false => None, + } + } +} + +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 157ea4f..d747ac3 100644 --- a/rust/tests/tests.rs +++ b/rust/tests/tests.rs @@ -12,6 +12,7 @@ // 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; |