aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Pursell <dpursell@google.com>2024-03-29 11:35:35 -0700
committerDavid Pursell <dpursell@google.com>2024-04-24 10:38:18 -0700
commite57ea84d3f913230345b3a0090f160f878d6ef9c (patch)
tree8daec718317b5fb5d9968ec9e9cacc90a2caf357
parent87b736478963f449fa00b187f2be0ba384d3a943 (diff)
downloadavb-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.bp32
-rw-r--r--rust/src/cert.rs43
-rw-r--r--rust/src/error.rs41
-rw-r--r--rust/src/lib.rs6
-rw-r--r--rust/src/ops.rs145
-rw-r--r--rust/tests/cert_tests.rs132
-rw-r--r--rust/tests/test_data.rs12
-rw-r--r--rust/tests/test_ops.rs68
-rw-r--r--rust/tests/tests.rs1
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;