diff options
author | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-05-10 15:34:23 +0000 |
---|---|---|
committer | Android Build Coastguard Worker <android-build-coastguard-worker@google.com> | 2024-05-10 15:34:23 +0000 |
commit | 1b9ced4dfea56ae3621a1b970089318b80590364 (patch) | |
tree | b6725d91c88dd8c52e9efa011d743a62d1c093dd | |
parent | 467c0d3ad610e167155572d538d9ec10282ec1f6 (diff) | |
parent | 605ae13dda96ebdfb651956a9c50f8ae3ef38eb8 (diff) | |
download | avb-busytown-mac-infra-release.tar.gz |
Snap for 11819167 from 605ae13dda96ebdfb651956a9c50f8ae3ef38eb8 to busytown-mac-infra-releasebusytown-mac-infra-release
Change-Id: I693ef69270abe008cf561f9f1afaf9653810a002
-rw-r--r-- | Android.bp | 88 | ||||
-rw-r--r-- | README.md | 123 | ||||
-rw-r--r-- | TEST_MAPPING | 2 | ||||
-rwxr-xr-x | avbtool.py | 300 | ||||
-rw-r--r-- | boot_control/boot_control_avb.c | 2 | ||||
-rw-r--r-- | examples/cert/README.md | 6 | ||||
-rw-r--r-- | examples/cert/avb_cert_slot_verify.c (renamed from examples/things/avb_atx_slot_verify.c) | 78 | ||||
-rw-r--r-- | examples/cert/avb_cert_slot_verify.h (renamed from examples/things/avb_atx_slot_verify.h) | 41 | ||||
-rw-r--r-- | examples/things/README.md | 6 | ||||
-rw-r--r-- | examples/uefi/main.c | 7 | ||||
-rw-r--r-- | examples/uefi/uefi_avb_boot.c | 3 | ||||
-rw-r--r-- | libavb/avb_chain_partition_descriptor.c | 1 | ||||
-rw-r--r-- | libavb/avb_chain_partition_descriptor.h | 16 | ||||
-rw-r--r-- | libavb/avb_ops.h | 10 | ||||
-rw-r--r-- | libavb/avb_slot_verify.c | 236 | ||||
-rw-r--r-- | libavb/avb_sysdeps.h | 9 | ||||
-rw-r--r-- | libavb/avb_sysdeps_posix.c | 25 | ||||
-rw-r--r-- | libavb/avb_util.h | 129 | ||||
-rw-r--r-- | libavb/avb_version.h | 2 | ||||
-rw-r--r-- | libavb_ab/avb_ab_flow.c | 26 | ||||
-rw-r--r-- | libavb_atx/avb_atx_types.h | 96 | ||||
-rw-r--r-- | libavb_cert/avb_cert_ops.h (renamed from libavb_atx/avb_atx_ops.h) | 28 | ||||
-rw-r--r-- | libavb_cert/avb_cert_types.h | 96 | ||||
-rw-r--r-- | libavb_cert/avb_cert_validate.c (renamed from libavb_atx/avb_atx_validate.c) | 156 | ||||
-rw-r--r-- | libavb_cert/avb_cert_validate.h (renamed from libavb_atx/avb_atx_validate.h) | 51 | ||||
-rw-r--r-- | libavb_cert/libavb_cert.h (renamed from libavb_atx/libavb_atx.h) | 18 | ||||
-rw-r--r-- | libavb_user/avb_ops_user.cpp | 12 | ||||
-rw-r--r-- | libavb_user/avb_user_verification.c | 32 | ||||
-rw-r--r-- | libavb_user/avb_user_verity.c | 32 | ||||
-rw-r--r-- | rust/Android.bp | 520 | ||||
-rw-r--r-- | rust/OWNERS | 2 | ||||
-rw-r--r-- | rust/TEST_MAPPING | 19 | ||||
-rw-r--r-- | rust/bindgen/avb.h | 20 | ||||
-rw-r--r-- | rust/src/cert.rs | 399 | ||||
-rw-r--r-- | rust/src/descriptor/chain.rs | 117 | ||||
-rw-r--r-- | rust/src/descriptor/commandline.rs | 102 | ||||
-rw-r--r-- | rust/src/descriptor/hash.rs | 131 | ||||
-rw-r--r-- | rust/src/descriptor/hashtree.rs | 157 | ||||
-rw-r--r-- | rust/src/descriptor/mod.rs | 271 | ||||
-rw-r--r-- | rust/src/descriptor/property.rs | 108 | ||||
-rw-r--r-- | rust/src/descriptor/util.rs | 178 | ||||
-rw-r--r-- | rust/src/error.rs | 380 | ||||
-rw-r--r-- | rust/src/lib.rs | 53 | ||||
-rw-r--r-- | rust/src/ops.rs | 1343 | ||||
-rw-r--r-- | rust/src/verify.rs | 460 | ||||
-rw-r--r-- | rust/testdata/chain_partition_descriptor.bin | bin | 0 -> 2160 bytes | |||
-rw-r--r-- | rust/testdata/hash_descriptor.bin | bin | 0 -> 176 bytes | |||
-rw-r--r-- | rust/testdata/hashtree_descriptor.bin | bin | 0 -> 240 bytes | |||
-rw-r--r-- | rust/testdata/kernel_commandline_descriptor.bin | bin | 0 -> 64 bytes | |||
-rw-r--r-- | rust/testdata/property_descriptor.bin | bin | 0 -> 64 bytes | |||
-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 | 416 | ||||
-rw-r--r-- | rust/tests/tests.rs | 53 | ||||
-rw-r--r-- | rust/tests/verify_tests.rs | 796 | ||||
-rw-r--r-- | test/Android.bp | 42 | ||||
-rw-r--r-- | test/at_auth_unlock_unittest.py | 24 | ||||
-rwxr-xr-x | test/avb_cert_generate_test_data (renamed from test/avb_atx_generate_test_data) | 68 | ||||
-rw-r--r-- | test/avb_cert_slot_verify_unittest.cc (renamed from test/avb_atx_slot_verify_unittest.cc) | 100 | ||||
-rw-r--r-- | test/avb_cert_validate_unittest.cc (renamed from test/avb_atx_validate_unittest.cc) | 333 | ||||
-rw-r--r-- | test/avb_slot_verify_unittest.cc | 214 | ||||
-rw-r--r-- | test/avb_sysdeps_posix_testing.cc | 7 | ||||
-rw-r--r-- | test/avbtool_unittest.cc | 184 | ||||
-rw-r--r-- | test/data/cert_metadata.bin (renamed from test/data/atx_metadata.bin) | bin | 3244 -> 3244 bytes | |||
-rw-r--r-- | test/data/cert_permanent_attributes.bin (renamed from test/data/atx_permanent_attributes.bin) | bin | 1052 -> 1052 bytes | |||
-rw-r--r-- | test/data/cert_pik_certificate.bin (renamed from test/data/atx_pik_certificate.bin) | bin | 1620 -> 1620 bytes | |||
-rw-r--r-- | test/data/cert_product_id.bin (renamed from test/data/atx_product_id.bin) | bin | 16 -> 16 bytes | |||
-rw-r--r-- | test/data/cert_psk_certificate.bin (renamed from test/data/atx_psk_certificate.bin) | bin | 1620 -> 1620 bytes | |||
-rw-r--r-- | test/data/cert_puk_certificate.bin (renamed from test/data/atx_puk_certificate.bin) | bin | 1620 -> 1620 bytes | |||
-rw-r--r-- | test/data/cert_unlock_challenge.bin (renamed from test/data/atx_unlock_challenge.bin) | 0 | ||||
-rw-r--r-- | test/data/cert_unlock_credential.bin (renamed from test/data/atx_unlock_credential.bin) | bin | 3756 -> 3756 bytes | |||
-rw-r--r-- | test/data/testkey_cert_pik.pem (renamed from test/data/testkey_atx_pik.pem) | 0 | ||||
-rw-r--r-- | test/data/testkey_cert_prk.pem (renamed from test/data/testkey_atx_prk.pem) | 0 | ||||
-rw-r--r-- | test/data/testkey_cert_psk.pem (renamed from test/data/testkey_atx_psk.pem) | 0 | ||||
-rw-r--r-- | test/data/testkey_cert_puk.pem (renamed from test/data/testkey_atx_puk.pem) | 0 | ||||
-rw-r--r-- | test/fake_avb_ops.cc | 32 | ||||
-rw-r--r-- | test/fake_avb_ops.h | 22 | ||||
-rw-r--r-- | test/user_code_test.cc | 2 | ||||
-rwxr-xr-x | tools/at_auth_unlock.py | 20 | ||||
-rw-r--r-- | tools/transparency/verify/internal/checkpoint/checkpoint.go | 28 | ||||
-rw-r--r-- | tools/transparency/verify/internal/checkpoint/checkpoint_test.go | 12 |
81 files changed, 7466 insertions, 1140 deletions
@@ -145,8 +145,9 @@ python_binary_host { compile_multilib: "first", } +// Default common to both standard and baremetal versions of libavb. cc_defaults { - name: "libavb_defaults", + name: "libavb_base_defaults", defaults: [ "avb_defaults", "avb_sources", @@ -158,11 +159,13 @@ cc_defaults { export_header_lib_headers: ["avb_headers"], } -// Build libavb - this is a static library that depends -// on only libc and libcrypto, but no other dependencies. -cc_library_static { - name: "libavb", - defaults: ["libavb_defaults"], +// Defaults for standard libavb; depends on only libc and libcrypto. +// +// The standard targets enable more logging and uses the standard versions of +// the dependencies; see the baremetal variant for a slimmer alternative. +cc_defaults { + name: "libavb_standard_defaults", + defaults: ["libavb_base_defaults"], host_supported: true, ramdisk_available: true, vendor_ramdisk_available: true, @@ -174,18 +177,48 @@ cc_library_static { linux: { srcs: ["libavb/avb_sysdeps_posix.c"], }, + darwin: { + enabled: true, + srcs: ["libavb/avb_sysdeps_posix.c"], + }, host_linux: { cflags: ["-fno-stack-protector"], }, }, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } -// A variant of the library that can run in baremetal environments. +// libavb +cc_library_static { + name: "libavb", + defaults: ["libavb_standard_defaults"], +} + +// libavb + cert // -// The debug feature isn't enabled, removing verbose logging and assertions. +// The cert extensions provides some additional support for minimal +// certificate-based signing. cc_library_static { - name: "libavb_baremetal", - defaults: ["libavb_defaults"], + name: "libavb_cert", + defaults: [ + "avb_cert_sources", + "libavb_standard_defaults", + ], +} + +// Defaults for a variant of libavb that can run in baremetal environments. +// +// The debug feature isn't enabled, removing verbose logging and assertions. +// Also uses the baremetal variant of the dependencies. +// +// This does still require a handful of Posix APIs as used by the sysdeps +// implementation. +cc_defaults { + name: "libavb_baremetal_defaults", + defaults: ["libavb_base_defaults"], cflags: ["-UAVB_ENABLE_DEBUG"], static_libs: [ "libcrypto_baremetal", @@ -193,6 +226,21 @@ cc_library_static { srcs: ["libavb/avb_sysdeps_posix.c"], } +// Baremetal libavb +cc_library_static { + name: "libavb_baremetal", + defaults: ["libavb_baremetal_defaults"], +} + +// Baremetal libavb + cert +cc_library_static { + name: "libavb_cert_baremetal", + defaults: [ + "avb_cert_sources", + "libavb_baremetal_defaults", + ], +} + // Build libavb_user for the target - in addition to libavb, it // includes libavb_ab, libavb_user and also depends on libbase and // libfs_mgr. @@ -254,8 +302,8 @@ cc_library_host_static { } cc_defaults { - name: "avb_atx_sources", - srcs: ["libavb_atx/avb_atx_validate.c"], + name: "avb_cert_sources", + srcs: ["libavb_cert/avb_cert_validate.c"], } cc_library_host_static { @@ -269,8 +317,8 @@ cc_library_host_static { } cc_defaults { - name: "avb_things_example_sources", - srcs: ["examples/things/avb_atx_slot_verify.c"], + name: "avb_cert_example_sources", + srcs: ["examples/cert/avb_cert_slot_verify.c"], } cc_defaults { @@ -278,8 +326,8 @@ cc_defaults { defaults: [ "avb_defaults", "avb_sources", - "avb_atx_sources", - "avb_things_example_sources", + "avb_cert_sources", + "avb_cert_example_sources", ], required: [ "simg2img", @@ -314,8 +362,8 @@ cc_defaults { ], srcs: [ "test/avb_ab_flow_unittest.cc", - "test/avb_atx_validate_unittest.cc", - "test/avb_atx_slot_verify_unittest.cc", + "test/avb_cert_validate_unittest.cc", + "test/avb_cert_slot_verify_unittest.cc", "test/avb_crypto_ops_unittest.cc", "test/avb_slot_verify_unittest.cc", "test/avb_unittest_util.cc", @@ -393,4 +441,8 @@ cc_library_headers { enabled: true, }, }, + apex_available: [ + "//apex_available:platform", + "com.android.virt", + ], } @@ -6,33 +6,32 @@ Verified Boot 2.0. Usually AVB is used to refer to this codebase. # Table of Contents -* [What is it?](#What-is-it) - + [The VBMeta struct](#The-VBMeta-struct) - + [Rollback Protection](#Rollback-Protection) - + [A/B Support](#A_B-Support) - + [The VBMeta Digest](#The-VBMeta-Digest) -* [Tools and Libraries](#Tools-and-Libraries) +* [What is it?](#what-is-it) + + [The VBMeta struct](#the-vbmeta-struct) + + [Rollback Protection](#rollback-Protection) + + [A/B Support](#a_b-Support) + + [The VBMeta Digest](#the-vbmeta-digest) +* [Tools and Libraries](#tools-and-libraries) + [avbtool and libavb](#avbtool-and-libavb) - + [Files and Directories](#Files-and-Directories) - + [Portability](#Portability) - + [Versioning and Compatibility](#Versioning-and-Compatibility) - + [Adding New Features](#Adding-New-Features) - + [Using avbtool](#Using-avbtool) - + [Build System Integration](#Build-System-Integration) -* [Device Integration](#Device-Integration) - + [System Dependencies](#System-Dependencies) - + [Locked and Unlocked mode](#Locked-and-Unlocked-mode) - + [Tamper-evident Storage](#Tamper_evident-Storage) - + [Named Persistent Values](#Named-Persistent-Values) - + [Persistent Digests](#Persistent-Digests) - + [Updating Stored Rollback Indexes](#Updating-Stored-Rollback-Indexes) - + [Recommended Bootflow](#Recommended-Bootflow) - + [Booting Into Recovery](#Booting-Into-Recovery) - + [Handling dm-verity Errors](#Handling-dm_verity-Errors) - + [Android Specific Integration](#Android-Specific-Integration) - + [GKI 2.0 Integration](#GKI-2_0-Integration) - + [Device Specific Notes](#Device-Specific-Notes) -* [Version History](#Version-History) + + [Files and Directories](#files-and-directories) + + [Portability](#portability) + + [Versioning and Compatibility](#versioning-and-compatibility) + + [Adding New Features](#adding-new-features) + + [Using avbtool](#using-avbtool) + + [Build System Integration](#build-system-integration) +* [Device Integration](#device-integration) + + [System Dependencies](#system-dependencies) + + [Locked and Unlocked mode](#locked-and-unlocked-mode) + + [Tamper-evident Storage](#tamper_evident-storage) + + [Named Persistent Values](#named-persistent-values) + + [Persistent Digests](#persistent-digests) + + [Updating Stored Rollback Indexes](#updating-stored-rollback-indexes) + + [Recommended Bootflow](#recommended-bootflow) + + [Booting Into Recovery](#booting-into-recovery) + + [Handling dm-verity Errors](#handling-dm_verity-errors) + + [Android Specific Integration](#android-specific-integration) + + [Device Specific Notes](#device-specific-notes) +* [Version History](#version-history) # What is it? @@ -125,7 +124,7 @@ Rollback protection is having the device reject an image unless having the device increase `stored_rollback_index[n]` over time. Exactly how this is done is discussed in the -[Updating Stored Rollback Indexes](#Updating-Stored-Rollback-Indexes) +[Updating Stored Rollback Indexes](#updating-stored-rollback-indexes) section. ## A/B Support @@ -145,6 +144,11 @@ possible to work with a partition that does not use A/B and should never have the prefix. This corresponds to the `AVB_HASH[TREE]_DESCRIPTOR_FLAGS_DO_NOT_USE_AB` flags. +In version 1.3, avbtool supports `chain_partition_do_not_use_ab` for +`make_vbmeta_image` operations. This makes it possible to work with +a chain partition that does not use A/B and should not have the suffix. +This corresponds to the `AVB_CHAIN_PARTITION_DESCRIPTOR_FLAGS_DO_NOT_USE_AB` flag. + ## The VBMeta Digest The VBMeta digest is a digest over all VBMeta structs including the root struct @@ -225,11 +229,17 @@ well as operations that the boot loader or OS is expected to implement (see `avb_ops.h`). The main entry point for verification is `avb_slot_verify()`. -Android Things has specific requirements and validation logic for the -vbmeta public key. An extension is provided in `libavb_atx` which -performs this validation as an implementation of `libavb`'s public key -validation operation (see `avb_validate_vbmeta_public_key()` in -`avb_ops.h`). +An optional extension `libavb_cert` additionally provides a scalable +certificate-based authorization mechanism. The base `libavb` requires +the device to implement public key validation manually (see +`avb_validate_vbmeta_public_key()` in `avb_ops.h`), which can be +complicated when working with anything other than a single hardcoded +key. `libavb_cert` provides an implementation of this function which +provides built-in support for features such as key rotation. + +`libavb_cert` was previously named `libavb_atx` (Android Things eXtension) but +it has been renamed to better represent its usefulness as a general-purpose +extension rather than anything specific to the Android Things project. ## Files and Directories @@ -243,8 +253,8 @@ validation operation (see `avb_validate_vbmeta_public_key()` in expected to be provided by the platform is defined in `avb_sysdeps.h`. If the platform provides the standard C runtime `avb_sysdeps_posix.c` can be used. -* `libavb_atx/` - + An Android Things Extension for validating public key metadata. +* `libavb_cert/` + + A libavb extension for certificate-based authorization. * `libavb_user/` + Contains an `AvbOps` implementation suitable for use in Android userspace. This is used in `boot_control.avb` and `avbctl`. @@ -267,16 +277,15 @@ validation operation (see `avb_validate_vbmeta_public_key()` in verified boot. * `test/` + Unit tests for `abvtool`, `libavb`, `libavb_ab`, and - `libavb_atx`. + `libavb_cert`. * `tools/avbctl/` + Contains the source-code for a tool that can be used to control AVB at runtime in Android. * `examples/uefi/` + Contains the source-code for a UEFI-based boot-loader utilizing `libavb/` and `libavb_ab/`. -* `examples/things/` - + Contains the source-code for a slot verification suitable for Android - Things. +* `examples/cert/` + + Contains example source-code for using the `avb_cert` extension * `README.md` + This file. * `docs/` @@ -371,17 +380,18 @@ there is obviously no need to bump it again. The content for the vbmeta partition can be generated as follows: - $ avbtool make_vbmeta_image \ - [--output OUTPUT] \ - [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key] \ - [--public_key_metadata /path/to/pkmd.bin] \ - [--rollback_index NUMBER] [--rollback_index_location NUMBER] \ - [--include_descriptors_from_image /path/to/image.bin] \ - [--setup_rootfs_from_kernel /path/to/image.bin] \ - [--chain_partition part_name:rollback_index_location:/path/to/key1.bin] \ - [--signing_helper /path/to/external/signer] \ - [--signing_helper_with_files /path/to/external/signer_with_files] \ - [--print_required_libavb_version] \ + $ avbtool make_vbmeta_image \ + [--output OUTPUT] \ + [--algorithm ALGORITHM] [--key /path/to/key_used_for_signing_or_pub_key] \ + [--public_key_metadata /path/to/pkmd.bin] \ + [--rollback_index NUMBER] [--rollback_index_location NUMBER] \ + [--include_descriptors_from_image /path/to/image.bin] \ + [--setup_rootfs_from_kernel /path/to/image.bin] \ + [--chain_partition part_name:rollback_index_location:/path/to/key1.bin] \ + [--chain_partition_do_not_use_ab part_name:rollback_index_location:/path/to/key.bin] \ + [--signing_helper /path/to/external/signer] \ + [--signing_helper_with_files /path/to/external/signer_with_files] \ + [--print_required_libavb_version] \ [--append_to_release_string STR] An integrity footer containing the hash for an entire partition can be @@ -663,10 +673,10 @@ e.g. derive `AVB_pk`. Both `AVB_pk` and `AVB_pkmd` are passed to the `validate_vbmeta_public_key()` operation when verifying a slot. Some devices may support the end-user configuring the root of trust to use, see -the [Device Specific Notes](#Device-Specific-Notes) section for details. +the [Device Specific Notes](#device-specific-notes) section for details. Devices can be configured to create additional `vbmeta` partitions as -[chained partitions](#The-VBMeta-struct) in order to update a subset of +[chained partitions](#the-vbmeta-struct) in order to update a subset of partitions without changing the top-level `vbmeta` partition. For example, the following variables create `vbmeta_system.img` as a chained `vbmeta` image that contains the hash-tree descriptors for `system.img`, `system_ext.img` @@ -1148,6 +1158,11 @@ part of the boot process to remind the user that the custom key is in use. # Version History +### Version 1.3 +Version 1.3 adds support for the following: +* A 32-bit `flags` element is added to a chain descriptor. +* Support for chain partitions which don't use [A/B](#a_b-support). + ### Version 1.2 Version 1.2 adds support for the following: @@ -1159,9 +1174,9 @@ Version 1.2 adds support for the following: Version 1.1 adds support for the following: * A 32-bit `flags` element is added to hash and hashtree descriptors. -* Support for partitions which don't use [A/B](#A_B-Support). -* Tamper-evident [named persistent values](#Named-Persistent-Values). -* [Persistent digests](#Persistent-Digests) for hash or hashtree descriptors. +* Support for partitions which don't use [A/B](#a_b-support). +* Tamper-evident [named persistent values](#named-persistent-values). +* [Persistent digests](#persistent-digests) for hash or hashtree descriptors. ### Version 1.0 diff --git a/TEST_MAPPING b/TEST_MAPPING index b97c31b..d848978 100644 --- a/TEST_MAPPING +++ b/TEST_MAPPING @@ -7,4 +7,4 @@ "name": "libavb_host_unittest_sha" } ] -} +}
\ No newline at end of file @@ -39,7 +39,7 @@ import time # Keep in sync with libavb/avb_version.h. AVB_VERSION_MAJOR = 1 -AVB_VERSION_MINOR = 2 +AVB_VERSION_MINOR = 3 AVB_VERSION_SUB = 0 # Keep in sync with libavb/avb_footer.h. @@ -51,6 +51,17 @@ AVB_VBMETA_IMAGE_FLAGS_HASHTREE_DISABLED = 1 # Configuration for enabling logging of calls to avbtool. AVB_INVOCATION_LOGFILE = os.environ.get('AVB_INVOCATION_LOGFILE') +# Known values for certificate "usage" field. These values must match the +# libavb_cert implementation. +# +# The "android.things" substring is only for historical reasons; these strings +# are used for the general-purpose libavb_cert extension and are not specific +# to the Android Things project. However, changing them would be a breaking +# change so it's simpler to leave them as-is. +CERT_USAGE_SIGNING = 'com.google.android.things.vboot' +CERT_USAGE_INTERMEDIATE_AUTHORITY = 'com.google.android.things.vboot.ca' +CERT_USAGE_UNLOCK = 'com.google.android.things.vboot.unlock' + class AvbError(Exception): """Application-specific errors. @@ -1805,15 +1816,17 @@ class AvbChainPartitionDescriptor(AvbDescriptor): rollback_index_location: The rollback index location to use. partition_name: Partition name as string. public_key: The public key as bytes. + flags: Descriptor flags (see avb_chain_partition_descriptor.h). """ TAG = 4 - RESERVED = 64 - SIZE = 28 + RESERVED + RESERVED = 60 + SIZE = 32 + RESERVED FORMAT_STRING = ('!QQ' # tag, num_bytes_following (descriptor header) 'L' # rollback_index_location 'L' # partition_name_size (bytes) 'L' + # public_key_size (bytes) + 'L' + # flags str(RESERVED) + 's') # reserved def __init__(self, data=None): @@ -1831,7 +1844,8 @@ class AvbChainPartitionDescriptor(AvbDescriptor): if data: (tag, num_bytes_following, self.rollback_index_location, partition_name_len, - public_key_len, _) = struct.unpack(self.FORMAT_STRING, data[0:self.SIZE]) + public_key_len, self.flags, _) = struct.unpack(self.FORMAT_STRING, + data[0:self.SIZE]) expected_size = round_to_multiple( self.SIZE - 16 + partition_name_len + public_key_len, 8) if tag != self.TAG or num_bytes_following != expected_size: @@ -1852,6 +1866,7 @@ class AvbChainPartitionDescriptor(AvbDescriptor): self.rollback_index_location = 0 self.partition_name = '' self.public_key = b'' + self.flags = 0 def print_desc(self, o): """Print the descriptor. @@ -1866,6 +1881,7 @@ class AvbChainPartitionDescriptor(AvbDescriptor): # Just show the SHA1 of the key, for size reasons. pubkey_digest = hashlib.sha1(self.public_key).hexdigest() o.write(' Public key (sha1): {}\n'.format(pubkey_digest)) + o.write(' Flags: {}\n'.format(self.flags)) def encode(self): """Serializes the descriptor. @@ -1881,7 +1897,7 @@ class AvbChainPartitionDescriptor(AvbDescriptor): desc = struct.pack(self.FORMAT_STRING, self.TAG, nbf_with_padding, self.rollback_index_location, len(partition_name_encoded), len(self.public_key), - self.RESERVED * b'\0') + self.flags, self.RESERVED * b'\0') ret = desc + partition_name_encoded + self.public_key + padding_size * b'\0' return ret @@ -2407,13 +2423,13 @@ class Avb(object): misc_image.seek(self.AB_MISC_METADATA_OFFSET) misc_image.write(ab_data) - def info_image(self, image_filename, output, atx): + def info_image(self, image_filename, output, cert): """Implements the 'info_image' command. Arguments: image_filename: Image file to get information from (file object). output: Output file to write human-readable information to (file object). - atx: If True, show information about Android Things eXtension (ATX). + cert: If True, show information about the avb_cert certificates. """ image = ImageHandler(image_filename, read_only=True) o = output @@ -2466,8 +2482,8 @@ class Avb(object): if num_printed == 0: o.write(' (none)\n') - if atx and header.public_key_metadata_size: - o.write('Android Things eXtension (ATX):\n') + if cert and header.public_key_metadata_size: + o.write('avb_cert certificate:\n') key_metadata_offset = (header.SIZE + header.authentication_data_block_size + header.public_key_metadata_offset) @@ -2476,7 +2492,7 @@ class Avb(object): version, pik, psk = struct.unpack('<I1620s1620s', key_metadata_blob) o.write(' Metadata version: {}\n'.format(version)) - def print_atx_certificate(cert): + def print_certificate(cert): version, public_key, subject, usage, key_version, _ = ( struct.unpack('<I1032s32s32sQ512s', cert)) o.write(' Version: {}\n'.format(version)) @@ -2487,9 +2503,9 @@ class Avb(object): o.write(' Key version: {}\n'.format(key_version)) o.write(' Product Intermediate Key:\n') - print_atx_certificate(pik) + print_certificate(pik) o.write(' Product Signing Key:\n') - print_atx_certificate(psk) + print_certificate(psk) def verify_image(self, image_filename, key_path, expected_chain_partitions, follow_chain_partitions, accept_zeroed_hashtree): @@ -2908,7 +2924,8 @@ class Avb(object): return self._get_cmdline_descriptors_for_hashtree_descriptor(ht) - def make_vbmeta_image(self, output, chain_partitions, algorithm_name, + def make_vbmeta_image(self, output, chain_partitions_use_ab, + chain_partitions_do_not_use_ab, algorithm_name, key_path, public_key_metadata_path, rollback_index, flags, rollback_index_location, props, props_from_file, kernel_cmdlines, @@ -2924,7 +2941,8 @@ class Avb(object): Arguments: output: File to write the image to. - chain_partitions: List of partitions to chain or None. + chain_partitions_use_ab: List of partitions to chain or None. + chain_partitions_do_not_use_ab: List of partitions to chain which does not use A/B or None. algorithm_name: Name of algorithm to use. key_path: Path to key to use or None. public_key_metadata_path: Path to public key metadata or None. @@ -2950,6 +2968,8 @@ class Avb(object): tmp_header = AvbVBMetaHeader() if rollback_index_location > 0: tmp_header.bump_required_libavb_version_minor(2) + if chain_partitions_do_not_use_ab: + tmp_header.bump_required_libavb_version_minor(3) if include_descriptors_from_image: # Use the bump logic in AvbVBMetaHeader to calculate the max required # version of all included descriptors. @@ -2970,8 +2990,8 @@ class Avb(object): ht_desc_to_setup = None vbmeta_blob = self._generate_vbmeta_blob( algorithm_name, key_path, public_key_metadata_path, descriptors, - chain_partitions, rollback_index, flags, rollback_index_location, - props, props_from_file, + chain_partitions_use_ab, chain_partitions_do_not_use_ab, + rollback_index, flags, rollback_index_location, props, props_from_file, kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, include_descriptors_from_image, signing_helper, signing_helper_with_files, release_string, @@ -2988,7 +3008,7 @@ class Avb(object): def _generate_vbmeta_blob(self, algorithm_name, key_path, public_key_metadata_path, descriptors, - chain_partitions, + chain_partitions_use_ab, chain_partitions_do_not_use_ab, rollback_index, flags, rollback_index_location, props, props_from_file, kernel_cmdlines, @@ -3013,7 +3033,8 @@ class Avb(object): key_path: The path to the .pem file used to sign the blob. public_key_metadata_path: Path to public key metadata or None. descriptors: A list of descriptors to insert or None. - chain_partitions: List of partitions to chain or None. + chain_partitions_use_ab: List of partitions to chain with A/B or None. + chain_partitions_do_not_use_ab: List of partitions to chain without A/B or None rollback_index: The rollback index to use. flags: Flags to use in the image. rollback_index_location: Location of the main vbmeta rollback index. @@ -3053,9 +3074,15 @@ class Avb(object): h.bump_required_libavb_version_minor(required_libavb_version_minor) # Insert chained partition descriptors, if any - if chain_partitions: + all_chain_partitions = [] + if chain_partitions_use_ab: + all_chain_partitions.extend(chain_partitions_use_ab) + if chain_partitions_do_not_use_ab: + all_chain_partitions.extend(chain_partitions_do_not_use_ab) + + if len(all_chain_partitions) > 0: used_locations = {rollback_index_location: True} - for cp in chain_partitions: + for cp in all_chain_partitions: cp_tokens = cp.split(':') if len(cp_tokens) != 3: raise AvbError('Malformed chained partition "{}".'.format(cp)) @@ -3075,6 +3102,8 @@ class Avb(object): raise AvbError('Rollback index location must be 1 or larger.') with open(file_path, 'rb') as f: desc.public_key = f.read() + if chain_partitions_do_not_use_ab and (cp in chain_partitions_do_not_use_ab): + desc.flags |= 1 descriptors.append(desc) # Descriptors. @@ -3335,8 +3364,9 @@ class Avb(object): def add_hash_footer(self, image_filename, partition_size, dynamic_partition_size, partition_name, - hash_algorithm, salt, chain_partitions, algorithm_name, - key_path, + hash_algorithm, salt, chain_partitions_use_ab, + chain_partitions_do_not_use_ab, + algorithm_name, key_path, public_key_metadata_path, rollback_index, flags, rollback_index_location, props, props_from_file, kernel_cmdlines, @@ -3356,7 +3386,8 @@ class Avb(object): partition_name: Name of partition (without A/B suffix). hash_algorithm: Hash algorithm to use. salt: Salt to use as a hexadecimal string or None to use /dev/urandom. - chain_partitions: List of partitions to chain. + chain_partitions_use_ab: List of partitions to chain with A/B or None. + chain_partitions_do_not_use_ab: List of partitions to chain without A/B or None. algorithm_name: Name of algorithm to use. key_path: Path to key to use or None. public_key_metadata_path: Path to public key metadata or None. @@ -3399,6 +3430,8 @@ class Avb(object): required_libavb_version_minor = 1 if rollback_index_location > 0: required_libavb_version_minor = 2 + if chain_partitions_do_not_use_ab: + required_libavb_version_minor = 3 # If we're asked to calculate minimum required libavb version, we're done. if print_required_libavb_version: @@ -3419,7 +3452,10 @@ class Avb(object): print('{}'.format(partition_size - max_metadata_size)) return - image = ImageHandler(image_filename) + # If we aren't appending the vbmeta footer to the input image we can + # open it in read-only mode. + image = ImageHandler(image_filename, + read_only=do_not_append_vbmeta_image) # If there's already a footer, truncate the image to its original # size. This way 'avbtool add_hash_footer' is idempotent (modulo @@ -3493,8 +3529,8 @@ class Avb(object): ht_desc_to_setup = None vbmeta_blob = self._generate_vbmeta_blob( algorithm_name, key_path, public_key_metadata_path, [h_desc], - chain_partitions, rollback_index, flags, rollback_index_location, - props, props_from_file, + chain_partitions_use_ab, chain_partitions_do_not_use_ab, rollback_index, + flags, rollback_index_location, props, props_from_file, kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, include_descriptors_from_image, signing_helper, signing_helper_with_files, release_string, @@ -3550,8 +3586,9 @@ class Avb(object): def add_hashtree_footer(self, image_filename, partition_size, partition_name, generate_fec, fec_num_roots, hash_algorithm, - block_size, salt, chain_partitions, algorithm_name, - key_path, + block_size, salt, chain_partitions_use_ab, + chain_partitions_do_not_use_ab, + algorithm_name, key_path, public_key_metadata_path, rollback_index, flags, rollback_index_location, props, props_from_file, kernel_cmdlines, @@ -3579,7 +3616,8 @@ class Avb(object): hash_algorithm: Hash algorithm to use. block_size: Block size to use. salt: Salt to use as a hexadecimal string or None to use /dev/urandom. - chain_partitions: List of partitions to chain. + chain_partitions_use_ab: List of partitions to chain. + chain_partitions_do_not_use_ab: List of partitions to chain without A/B or None. algorithm_name: Name of algorithm to use. key_path: Path to key to use or None. public_key_metadata_path: Path to public key metadata or None. @@ -3620,6 +3658,8 @@ class Avb(object): required_libavb_version_minor = 1 if rollback_index_location > 0: required_libavb_version_minor = 2 + if chain_partitions_do_not_use_ab: + required_libavb_version_minor = 3 # If we're asked to calculate minimum required libavb version, we're done. if print_required_libavb_version: @@ -3791,7 +3831,8 @@ class Avb(object): vbmeta_offset = tree_offset + len_hashtree_and_fec vbmeta_blob = self._generate_vbmeta_blob( algorithm_name, key_path, public_key_metadata_path, [ht_desc], - chain_partitions, rollback_index, flags, rollback_index_location, + chain_partitions_use_ab, chain_partitions_do_not_use_ab, + rollback_index, flags, rollback_index_location, props, props_from_file, kernel_cmdlines, setup_rootfs_from_kernel, ht_desc_to_setup, include_descriptors_from_image, signing_helper, @@ -3832,16 +3873,15 @@ class Avb(object): image.truncate(original_image_size) raise AvbError('Adding hashtree_footer failed: {}.'.format(e)) from e - def make_atx_certificate(self, output, authority_key_path, subject_key_path, - subject_key_version, subject, - is_intermediate_authority, usage, signing_helper, - signing_helper_with_files): - """Implements the 'make_atx_certificate' command. + def make_certificate(self, output, authority_key_path, subject_key_path, + subject_key_version, subject, usage, + signing_helper, signing_helper_with_files): + """Implements the 'make_certificate' command. - Android Things certificates are required for Android Things public key - metadata. They chain the vbmeta signing key for a particular product back to - a fused, permanent root key. These certificates are fixed-length and fixed- - format with the explicit goal of not parsing ASN.1 in bootloader code. + Certificates are required for avb_cert extension public key metadata. They + chain the vbmeta signing key for a particular product back to a fused, + permanent root key. These certificates are fixed-length and fixed-format + with the explicit goal of not parsing ASN.1 in bootloader code. Arguments: output: Certificate will be written to this file on success. @@ -3854,9 +3894,7 @@ class Avb(object): of seconds since the epoch is used. subject: A subject identifier. For Product Signing Key certificates this should be the same Product ID found in the permanent attributes. - is_intermediate_authority: True if the certificate is for an intermediate - authority. - usage: If not empty, overrides the cert usage with a hash of this value. + usage: Usage string whose SHA256 hash will be embedded in the certificate. signing_helper: Program which signs a hash and returns the signature. signing_helper_with_files: Same as signing_helper but uses files instead. @@ -3869,10 +3907,6 @@ class Avb(object): hasher = hashlib.sha256() hasher.update(subject) signed_data.extend(hasher.digest()) - if not usage: - usage = 'com.google.android.things.vboot' - if is_intermediate_authority: - usage += '.ca' hasher = hashlib.sha256() hasher.update(usage.encode('ascii')) signed_data.extend(hasher.digest()) @@ -3888,11 +3922,11 @@ class Avb(object): output.write(signed_data) output.write(signature) - def make_atx_permanent_attributes(self, output, root_authority_key_path, - product_id): - """Implements the 'make_atx_permanent_attributes' command. + def make_cert_permanent_attributes(self, output, root_authority_key_path, + product_id): + """Implements the 'make_cert_permanent_attributes' command. - Android Things permanent attributes are designed to be permanent for a + avb_cert permanent attributes are designed to be permanent for a particular product and a hash of these attributes should be fused into hardware to enforce this. @@ -3912,22 +3946,22 @@ class Avb(object): output.write(RSAPublicKey(root_authority_key_path).encode()) output.write(product_id) - def make_atx_metadata(self, output, intermediate_key_certificate, - product_key_certificate): - """Implements the 'make_atx_metadata' command. + def make_cert_metadata(self, output, intermediate_key_certificate, + product_key_certificate): + """Implements the 'make_cert_metadata' command. - Android Things metadata are included in vbmeta images to facilitate + avb_cert metadata are included in vbmeta images to facilitate verification. The output of this command can be used as the public_key_metadata argument to other commands. Arguments: output: Metadata will be written to this file on success. intermediate_key_certificate: A certificate file as output by - make_atx_certificate with - is_intermediate_authority set to true. + make_certificate with usage set to + CERT_USAGE_INTERMEDIATE_AUTHORITY. product_key_certificate: A certificate file as output by - make_atx_certificate with - is_intermediate_authority set to false. + make_certificate with usage set to + CERT_USAGE_SIGNING. Raises: AvbError: If an argument is incorrect. @@ -3941,14 +3975,14 @@ class Avb(object): output.write(intermediate_key_certificate) output.write(product_key_certificate) - def make_atx_unlock_credential(self, output, intermediate_key_certificate, - unlock_key_certificate, challenge_path, - unlock_key_path, signing_helper, - signing_helper_with_files): - """Implements the 'make_atx_unlock_credential' command. + def make_cert_unlock_credential(self, output, intermediate_key_certificate, + unlock_key_certificate, challenge_path, + unlock_key_path, signing_helper, + signing_helper_with_files): + """Implements the 'make_cert_unlock_credential' command. - Android Things unlock credentials can be used to authorize the unlock of AVB - on a device. These credentials are presented to an Android Things bootloader + avb_cert unlock credentials can be used to authorize the unlock of AVB + on a device. These credentials are presented to an avb_cert bootloader via the fastboot interface in response to a 16-byte challenge. This method creates all fields of the credential except the challenge signature field (which is the last field) and can optionally create the challenge signature @@ -3957,13 +3991,11 @@ class Avb(object): Arguments: output: The credential will be written to this file on success. intermediate_key_certificate: A certificate file as output by - make_atx_certificate with - is_intermediate_authority set to true. + make_certificate with usage set to + CERT_USAGE_INTERMEDIATE_AUTHORITY. unlock_key_certificate: A certificate file as output by - make_atx_certificate with - is_intermediate_authority set to false and the - usage set to - 'com.google.android.things.vboot.unlock'. + make_certificate with usage set to + CERT_USAGE_UNLOCK. challenge_path: [optional] A path to the challenge to sign. unlock_key_path: [optional] A PEM file path with the unlock private key. signing_helper: Program which signs a hash and returns the signature. @@ -4250,6 +4282,11 @@ class AvbTool(object): help='Allow signed integrity-data for partition', metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH', action='append') + sub_parser.add_argument('--chain_partition_do_not_use_ab', + help='Allow signed integrity-data for partition does not use A/B', + metavar='PART_NAME:ROLLBACK_SLOT:KEY_PATH', + action='append', + required=False) sub_parser.add_argument('--flags', help='VBMeta flags', type=parse_number, @@ -4350,8 +4387,7 @@ class AvbTool(object): sub_parser = subparsers.add_parser('add_hash_footer', help='Add hashes and footer to image.') sub_parser.add_argument('--image', - help='Image to add hashes to', - type=argparse.FileType('rb+')) + help='Image to add hashes to') sub_parser.add_argument('--partition_size', help='Partition size', type=parse_number) @@ -4525,9 +4561,9 @@ class AvbTool(object): help='Write info to file', type=argparse.FileType('wt'), default=sys.stdout) - sub_parser.add_argument('--atx', - help=('Show information about Android Things ' - 'eXtension (ATX).'), + sub_parser.add_argument('--cert', '--atx', + help=('Show information about the avb_cert ' + 'extension certificate.'), action='store_true') sub_parser.set_defaults(func=self.info_image) @@ -4622,8 +4658,9 @@ class AvbTool(object): sub_parser.set_defaults(func=self.set_ab_metadata) sub_parser = subparsers.add_parser( - 'make_atx_certificate', - help='Create an Android Things eXtension (ATX) certificate.') + 'make_certificate', + aliases=['make_atx_certificate'], + help='Create an avb_cert extension certificate.') sub_parser.add_argument('--output', help='Write certificate to file', type=argparse.FileType('wb'), @@ -4640,14 +4677,25 @@ class AvbTool(object): help=('Version of the subject key'), type=parse_number, required=False) - sub_parser.add_argument('--subject_is_intermediate_authority', - help=('Generate an intermediate authority ' - 'certificate'), - action='store_true') - sub_parser.add_argument('--usage', - help=('Override usage with a hash of the provided ' - 'string'), - required=False) + # We have 3 different usage modifying args for convenience, at most one of + # which can be provided since they all set the same usage field. + usage_group = sub_parser.add_mutually_exclusive_group(required=False) + usage_group.add_argument('--subject_is_intermediate_authority', + help=('Override usage with the value used for ' + 'an intermediate authority'), + action='store_const', + const=CERT_USAGE_INTERMEDIATE_AUTHORITY, + required=False) + usage_group.add_argument('--usage', + help=('Override usage with a hash of the provided ' + 'string'), + required=False), + usage_group.add_argument('--usage_for_unlock', + help=('Override usage with the value used for ' + 'authenticated unlock'), + action='store_const', + const=CERT_USAGE_UNLOCK, + required=False), sub_parser.add_argument('--authority_key', help='Path to authority RSA private key file', required=False) @@ -4661,11 +4709,12 @@ class AvbTool(object): metavar='APP', default=None, required=False) - sub_parser.set_defaults(func=self.make_atx_certificate) + sub_parser.set_defaults(func=self.make_certificate) sub_parser = subparsers.add_parser( - 'make_atx_permanent_attributes', - help='Create Android Things eXtension (ATX) permanent attributes.') + 'make_cert_permanent_attributes', + aliases=['make_atx_permanent_attributes'], + help='Create avb_cert extension permanent attributes.') sub_parser.add_argument('--output', help='Write attributes to file', type=argparse.FileType('wb'), @@ -4678,11 +4727,12 @@ class AvbTool(object): help=('Path to Product ID file'), type=argparse.FileType('rb'), required=True) - sub_parser.set_defaults(func=self.make_atx_permanent_attributes) + sub_parser.set_defaults(func=self.make_cert_permanent_attributes) sub_parser = subparsers.add_parser( - 'make_atx_metadata', - help='Create Android Things eXtension (ATX) metadata.') + 'make_cert_metadata', + aliases=['make_atx_metadata'], + help='Create avb_cert extension metadata.') sub_parser.add_argument('--output', help='Write metadata to file', type=argparse.FileType('wb'), @@ -4695,11 +4745,12 @@ class AvbTool(object): help='Path to product key certificate file', type=argparse.FileType('rb'), required=True) - sub_parser.set_defaults(func=self.make_atx_metadata) + sub_parser.set_defaults(func=self.make_cert_metadata) sub_parser = subparsers.add_parser( - 'make_atx_unlock_credential', - help='Create an Android Things eXtension (ATX) unlock credential.') + 'make_cert_unlock_credential', + aliases=['make_atx_unlock_credential'], + help='Create an avb_cert extension unlock credential.') sub_parser.add_argument('--output', help='Write credential to file', type=argparse.FileType('wb'), @@ -4732,7 +4783,7 @@ class AvbTool(object): metavar='APP', default=None, required=False) - sub_parser.set_defaults(func=self.make_atx_unlock_credential) + sub_parser.set_defaults(func=self.make_cert_unlock_credential) args = parser.parse_args(argv[1:]) try: @@ -4763,6 +4814,7 @@ class AvbTool(object): """Implements the 'make_vbmeta_image' sub-command.""" args = self._fixup_common_args(args) self.avb.make_vbmeta_image(args.output, args.chain_partition, + args.chain_partition_do_not_use_ab, args.algorithm, args.key, args.public_key_metadata, args.rollback_index, args.flags, args.rollback_index_location, @@ -4785,11 +4837,12 @@ class AvbTool(object): def add_hash_footer(self, args): """Implements the 'add_hash_footer' sub-command.""" args = self._fixup_common_args(args) - self.avb.add_hash_footer(args.image.name if args.image else None, + self.avb.add_hash_footer(args.image, args.partition_size, args.dynamic_partition_size, args.partition_name, args.hash_algorithm, - args.salt, args.chain_partition, args.algorithm, - args.key, + args.salt, args.chain_partition, + args.chain_partition_do_not_use_ab, + args.algorithm, args.key, args.public_key_metadata, args.rollback_index, args.flags, args.rollback_index_location, args.prop, args.prop_from_file, @@ -4822,7 +4875,9 @@ class AvbTool(object): args.partition_name, not args.do_not_generate_fec, args.fec_num_roots, args.hash_algorithm, args.block_size, - args.salt, args.chain_partition, args.algorithm, + args.salt, args.chain_partition, + args.chain_partition_do_not_use_ab, + args.algorithm, args.key, args.public_key_metadata, args.rollback_index, args.flags, args.rollback_index_location, args.prop, @@ -4867,7 +4922,7 @@ class AvbTool(object): def info_image(self, args): """Implements the 'info_image' sub-command.""" - self.avb.info_image(args.image.name, args.output, args.atx) + self.avb.info_image(args.image.name, args.output, args.cert) def verify_image(self, args): """Implements the 'verify_image' sub-command.""" @@ -4890,32 +4945,35 @@ class AvbTool(object): self.avb.calculate_kernel_cmdline(args.image.name, args.hashtree_disabled, args.output) - def make_atx_certificate(self, args): - """Implements the 'make_atx_certificate' sub-command.""" - self.avb.make_atx_certificate(args.output, args.authority_key, - args.subject_key.name, - args.subject_key_version, - args.subject.read(), - args.subject_is_intermediate_authority, - args.usage, - args.signing_helper, - args.signing_helper_with_files) - - def make_atx_permanent_attributes(self, args): - """Implements the 'make_atx_permanent_attributes' sub-command.""" - self.avb.make_atx_permanent_attributes(args.output, + def make_certificate(self, args): + """Implements the 'make_certificate' sub-command.""" + # argparse mutually exclusive group ensures that at most one of the usage + # args will exist. If none exist, default to signing usage. + usage = (args.subject_is_intermediate_authority or args.usage or + args.usage_for_unlock or CERT_USAGE_SIGNING) + self.avb.make_certificate(args.output, args.authority_key, + args.subject_key.name, + args.subject_key_version, + args.subject.read(), + usage, + args.signing_helper, + args.signing_helper_with_files) + + def make_cert_permanent_attributes(self, args): + """Implements the 'make_cert_permanent_attributes' sub-command.""" + self.avb.make_cert_permanent_attributes(args.output, args.root_authority_key.name, args.product_id.read()) - def make_atx_metadata(self, args): - """Implements the 'make_atx_metadata' sub-command.""" - self.avb.make_atx_metadata(args.output, + def make_cert_metadata(self, args): + """Implements the 'make_cert_metadata' sub-command.""" + self.avb.make_cert_metadata(args.output, args.intermediate_key_certificate.read(), args.product_key_certificate.read()) - def make_atx_unlock_credential(self, args): - """Implements the 'make_atx_unlock_credential' sub-command.""" - self.avb.make_atx_unlock_credential( + def make_cert_unlock_credential(self, args): + """Implements the 'make_cert_unlock_credential' sub-command.""" + self.avb.make_cert_unlock_credential( args.output, args.intermediate_key_certificate.read(), args.unlock_key_certificate.read(), diff --git a/boot_control/boot_control_avb.c b/boot_control/boot_control_avb.c index a4bdfe9..34bf13d 100644 --- a/boot_control/boot_control_avb.c +++ b/boot_control/boot_control_avb.c @@ -58,7 +58,7 @@ static unsigned int module_getCurrentSlot(boot_control_module_t* module) { } else if (strcmp(propbuf, "_b") == 0) { return 1; } else { - avb_errorv("Unexpected slot suffix '", propbuf, "'.\n", NULL); + avb_error("Unexpected slot suffix '", propbuf, "'.\n"); return 0; } return 0; diff --git a/examples/cert/README.md b/examples/cert/README.md new file mode 100644 index 0000000..3828b97 --- /dev/null +++ b/examples/cert/README.md @@ -0,0 +1,6 @@ +# avb_cert Example +--- + +This directory contains example source code for an integration of libavb and +the libavb_cert extension. The implementation includes rollback index +management and vbmeta hash computation. diff --git a/examples/things/avb_atx_slot_verify.c b/examples/cert/avb_cert_slot_verify.c index 0de6d2e..b0fcaa8 100644 --- a/examples/things/avb_atx_slot_verify.c +++ b/examples/cert/avb_cert_slot_verify.c @@ -22,11 +22,11 @@ * SOFTWARE. */ -#include "avb_atx_slot_verify.h" +#include "avb_cert_slot_verify.h" #include <libavb/avb_sha.h> #include <libavb/libavb.h> -#include <libavb_atx/libavb_atx.h> +#include <libavb_cert/libavb_cert.h> /* Chosen to be generous but still require a huge number of increase operations * before exhausting the 64-bit space. @@ -36,31 +36,31 @@ static const uint64_t kRollbackIndexIncreaseThreshold = 1000000000; /* By convention, when a rollback index is not used the value remains zero. */ static const uint64_t kRollbackIndexNotUsed = 0; -typedef struct _AvbAtxOpsContext { +typedef struct _AvbCertOpsContext { size_t key_version_location[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS]; uint64_t key_version_value[AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS]; size_t next_key_version_slot; -} AvbAtxOpsContext; +} AvbCertOpsContext; -typedef struct _AvbAtxOpsWithContext { - AvbAtxOps atx_ops; - AvbAtxOpsContext context; -} AvbAtxOpsWithContext; +typedef struct _AvbCertOpsWithContext { + AvbCertOps cert_ops; + AvbCertOpsContext context; +} AvbCertOpsWithContext; -/* Returns context associated with |atx_ops| returned by +/* Returns context associated with |cert_ops| returned by * setup_ops_with_context(). */ -static AvbAtxOpsContext* get_ops_context(AvbAtxOps* atx_ops) { - return &((AvbAtxOpsWithContext*)atx_ops)->context; +static AvbCertOpsContext* get_ops_context(AvbCertOps* cert_ops) { + return &((AvbCertOpsWithContext*)cert_ops)->context; } -/* An implementation of AvbAtxOps::set_key_version that saves the key version +/* An implementation of AvbCertOps::set_key_version that saves the key version * information to ops context data. */ -static void save_key_version_to_context(AvbAtxOps* atx_ops, +static void save_key_version_to_context(AvbCertOps* cert_ops, size_t rollback_index_location, uint64_t key_version) { - AvbAtxOpsContext* context = get_ops_context(atx_ops); + AvbCertOpsContext* context = get_ops_context(cert_ops); size_t offset = context->next_key_version_slot++; if (offset < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS) { context->key_version_location[offset] = rollback_index_location; @@ -73,14 +73,14 @@ static void save_key_version_to_context(AvbAtxOps* atx_ops, * The set_key_version function will be replaced in order to collect the key * version information in the context. */ -static AvbAtxOps* setup_ops_with_context( - const AvbAtxOps* existing_ops, AvbAtxOpsWithContext* ops_with_context) { - avb_memset(ops_with_context, 0, sizeof(AvbAtxOpsWithContext)); - ops_with_context->atx_ops = *existing_ops; +static AvbCertOps* setup_ops_with_context( + const AvbCertOps* existing_ops, AvbCertOpsWithContext* ops_with_context) { + avb_memset(ops_with_context, 0, sizeof(AvbCertOpsWithContext)); + ops_with_context->cert_ops = *existing_ops; // Close the loop on the circular reference. - ops_with_context->atx_ops.ops->atx_ops = &ops_with_context->atx_ops; - ops_with_context->atx_ops.set_key_version = save_key_version_to_context; - return &ops_with_context->atx_ops; + ops_with_context->cert_ops.ops->cert_ops = &ops_with_context->cert_ops; + ops_with_context->cert_ops.set_key_version = save_key_version_to_context; + return &ops_with_context->cert_ops; } /* Updates the stored rollback index value for |location| to match |value|. */ @@ -122,54 +122,54 @@ static AvbSlotVerifyResult update_rollback_index(AvbOps* ops, return AVB_SLOT_VERIFY_RESULT_OK; } -AvbSlotVerifyResult avb_atx_slot_verify( - AvbAtxOps* atx_ops, +AvbSlotVerifyResult avb_cert_slot_verify( + AvbCertOps* cert_ops, const char* ab_suffix, - AvbAtxLockState lock_state, - AvbAtxSlotState slot_state, - AvbAtxOemDataState oem_data_state, + AvbCertLockState lock_state, + AvbCertSlotState slot_state, + AvbCertOemDataState oem_data_state, AvbSlotVerifyData** verify_data, - uint8_t vbh_extension[AVB_SHA256_DIGEST_SIZE]) { + uint8_t vbmeta_digest[AVB_SHA256_DIGEST_SIZE]) { const char* partitions_without_oem[] = {"boot", NULL}; const char* partitions_with_oem[] = {"boot", "oem_bootloader", NULL}; AvbSlotVerifyResult result = AVB_SLOT_VERIFY_RESULT_OK; size_t i = 0; - AvbAtxOpsWithContext ops_with_context; + AvbCertOpsWithContext ops_with_context; - atx_ops = setup_ops_with_context(atx_ops, &ops_with_context); + cert_ops = setup_ops_with_context(cert_ops, &ops_with_context); - result = avb_slot_verify(atx_ops->ops, - (oem_data_state == AVB_ATX_OEM_DATA_NOT_USED) + result = avb_slot_verify(cert_ops->ops, + (oem_data_state == AVB_CERT_OEM_DATA_NOT_USED) ? partitions_without_oem : partitions_with_oem, ab_suffix, - (lock_state == AVB_ATX_UNLOCKED) + (lock_state == AVB_CERT_UNLOCKED) ? AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR : AVB_SLOT_VERIFY_FLAGS_NONE, AVB_HASHTREE_ERROR_MODE_EIO, verify_data); - if (result != AVB_SLOT_VERIFY_RESULT_OK || lock_state == AVB_ATX_UNLOCKED) { + if (result != AVB_SLOT_VERIFY_RESULT_OK || lock_state == AVB_CERT_UNLOCKED) { return result; } - /* Compute the Android Things Verified Boot Hash (VBH) extension. */ + /* Compute the vbmeta digest. */ avb_slot_verify_data_calculate_vbmeta_digest( - *verify_data, AVB_DIGEST_TYPE_SHA256, vbh_extension); + *verify_data, AVB_DIGEST_TYPE_SHA256, vbmeta_digest); /* Increase rollback index values to match the verified slot. */ - if (slot_state == AVB_ATX_SLOT_MARKED_SUCCESSFUL) { + if (slot_state == AVB_CERT_SLOT_MARKED_SUCCESSFUL) { for (i = 0; i < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; i++) { uint64_t rollback_index_value = (*verify_data)->rollback_indexes[i]; if (rollback_index_value != kRollbackIndexNotUsed) { - result = update_rollback_index(atx_ops->ops, i, rollback_index_value); + result = update_rollback_index(cert_ops->ops, i, rollback_index_value); if (result != AVB_SLOT_VERIFY_RESULT_OK) { goto out; } } } - /* Also increase rollback index values for Android Things key version + /* Also increase rollback index values for the avb_cert key version * locations. */ for (i = 0; i < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; i++) { @@ -179,7 +179,7 @@ AvbSlotVerifyResult avb_atx_slot_verify( ops_with_context.context.key_version_value[i]; if (rollback_index_value != kRollbackIndexNotUsed) { result = update_rollback_index( - atx_ops->ops, rollback_index_location, rollback_index_value); + cert_ops->ops, rollback_index_location, rollback_index_value); if (result != AVB_SLOT_VERIFY_RESULT_OK) { goto out; } diff --git a/examples/things/avb_atx_slot_verify.h b/examples/cert/avb_cert_slot_verify.h index 517b097..d2d82ce 100644 --- a/examples/things/avb_atx_slot_verify.h +++ b/examples/cert/avb_cert_slot_verify.h @@ -22,29 +22,29 @@ * SOFTWARE. */ -#ifndef AVB_ATX_SLOT_VERIFY_H_ -#define AVB_ATX_SLOT_VERIFY_H_ +#ifndef AVB_CERT_SLOT_VERIFY_H_ +#define AVB_CERT_SLOT_VERIFY_H_ -#include <libavb_atx/libavb_atx.h> +#include <libavb_cert/libavb_cert.h> #ifdef __cplusplus extern "C" { #endif typedef enum { - AVB_ATX_LOCKED, - AVB_ATX_UNLOCKED, -} AvbAtxLockState; + AVB_CERT_LOCKED, + AVB_CERT_UNLOCKED, +} AvbCertLockState; typedef enum { - AVB_ATX_SLOT_MARKED_SUCCESSFUL, - AVB_ATX_SLOT_NOT_MARKED_SUCCESSFUL, -} AvbAtxSlotState; + AVB_CERT_SLOT_MARKED_SUCCESSFUL, + AVB_CERT_SLOT_NOT_MARKED_SUCCESSFUL, +} AvbCertSlotState; typedef enum { - AVB_ATX_OEM_DATA_USED, - AVB_ATX_OEM_DATA_NOT_USED, -} AvbAtxOemDataState; + AVB_CERT_OEM_DATA_USED, + AVB_CERT_OEM_DATA_NOT_USED, +} AvbCertOemDataState; /* Performs a full verification of the slot identified by |ab_suffix|. If * |lock_state| indicates verified boot is unlocked then verification errors @@ -61,21 +61,20 @@ typedef enum { * * The semantics of |out_data| are the same as for avb_slot_verify(). * - * On success, an Android Things |vbh_extension| is populated. This value must - * be extended into the Verified Boot Hash value accumulated from earlier boot - * stages. + * On success, the SHA256 vbmeta digest is written to |vbmeta_digest|. This + * value may be used e.g. for device attestation. * * All of the function pointers in |ops| must be valid except for * set_key_version, which will be ignored and may be NULL. */ -AvbSlotVerifyResult avb_atx_slot_verify( - AvbAtxOps* ops, +AvbSlotVerifyResult avb_cert_slot_verify( + AvbCertOps* ops, const char* ab_suffix, - AvbAtxLockState lock_state, - AvbAtxSlotState slot_state, - AvbAtxOemDataState oem_data_state, + AvbCertLockState lock_state, + AvbCertSlotState slot_state, + AvbCertOemDataState oem_data_state, AvbSlotVerifyData** verify_data, - uint8_t vbh_extension[AVB_SHA256_DIGEST_SIZE]); + uint8_t vbmeta_digest[AVB_SHA256_DIGEST_SIZE]); #ifdef __cplusplus } diff --git a/examples/things/README.md b/examples/things/README.md deleted file mode 100644 index 45509af..0000000 --- a/examples/things/README.md +++ /dev/null @@ -1,6 +0,0 @@ -# Android Things Example ---- - -This directory contains example source code for an Android Things integration of -lib_avb and lib_avb_atx. The implementation includes rollback index management -and Verified Boot Hash (VBH) computation. diff --git a/examples/uefi/main.c b/examples/uefi/main.c index 01dae48..887ac08 100644 --- a/examples/uefi/main.c +++ b/examples/uefi/main.c @@ -103,10 +103,9 @@ EFI_STATUS EFIAPI efi_main(EFI_HANDLE ImageHandle, } boot_result = uefi_avb_boot_kernel(ImageHandle, slot_data, additional_cmdline); - avb_fatalv("uefi_avb_boot_kernel() failed with error ", - uefi_avb_boot_kernel_result_to_string(boot_result), - "\n", - NULL); + avb_fatal("uefi_avb_boot_kernel() failed with error ", + uefi_avb_boot_kernel_result_to_string(boot_result), + "\n"); avb_slot_verify_data_free(slot_data); avb_free(additional_cmdline); break; diff --git a/examples/uefi/uefi_avb_boot.c b/examples/uefi/uefi_avb_boot.c index 800c9a1..d79d899 100644 --- a/examples/uefi/uefi_avb_boot.c +++ b/examples/uefi/uefi_avb_boot.c @@ -142,8 +142,7 @@ UEFIAvbBootKernelResult uefi_avb_boot_kernel(EFI_HANDLE efi_image_handle, boot = &slot_data->loaded_partitions[0]; if (avb_strcmp(boot->partition_name, "boot") != 0) { - avb_errorv( - "Unexpected partition name '", boot->partition_name, "'.\n", NULL); + avb_error("Unexpected partition name '", boot->partition_name, "'.\n"); ret = UEFI_AVB_BOOT_KERNEL_RESULT_ERROR_PARTITION_INVALID_FORMAT; goto out; } diff --git a/libavb/avb_chain_partition_descriptor.c b/libavb/avb_chain_partition_descriptor.c index 3f14232..3adfe7d 100644 --- a/libavb/avb_chain_partition_descriptor.c +++ b/libavb/avb_chain_partition_descriptor.c @@ -43,6 +43,7 @@ bool avb_chain_partition_descriptor_validate_and_byteswap( dest->rollback_index_location = avb_be32toh(dest->rollback_index_location); dest->partition_name_len = avb_be32toh(dest->partition_name_len); dest->public_key_len = avb_be32toh(dest->public_key_len); + dest->flags = avb_be32toh(dest->flags); if (dest->rollback_index_location < 1) { avb_error("Invalid rollback index location value.\n"); diff --git a/libavb/avb_chain_partition_descriptor.h b/libavb/avb_chain_partition_descriptor.h index f2c9250..2a08afe 100644 --- a/libavb/avb_chain_partition_descriptor.h +++ b/libavb/avb_chain_partition_descriptor.h @@ -35,6 +35,16 @@ extern "C" { #endif +/* Flags for chain descriptors. + * + * AVB_CHAIN_PARTITION_DESCRIPTOR_FLAGS_DO_NOT_USE_AB: Do not apply the default A/B + * partition logic to this partition. This is intentionally a negative boolean + * because A/B should be both the default and most used in practice. + */ +typedef enum { + AVB_CHAIN_PARTITION_DESCRIPTOR_FLAGS_DO_NOT_USE_AB = (1 << 0), +} AvbChainPartitionDescriptorFlags; + /* A descriptor containing a pointer to signed integrity data stored * on another partition. The descriptor contains the partition name in * question (without the A/B suffix), the public key used to sign the @@ -47,13 +57,17 @@ extern "C" { * * The |reserved| field is for future expansion and must be set to NUL * bytes. + * + * Changes in v1.3: + * - flags field is added which supports AVB_CHAIN_PARTITION_DESCRIPTOR_FLAGS_DO_NOT_USE_AB */ typedef struct AvbChainPartitionDescriptor { AvbDescriptor parent_descriptor; uint32_t rollback_index_location; uint32_t partition_name_len; uint32_t public_key_len; - uint8_t reserved[64]; + uint32_t flags; + uint8_t reserved[60]; } AVB_ATTR_PACKED AvbChainPartitionDescriptor; /* Copies |src| to |dest| and validates, byte-swapping fields in the diff --git a/libavb/avb_ops.h b/libavb/avb_ops.h index dec163d..f037eae 100644 --- a/libavb/avb_ops.h +++ b/libavb/avb_ops.h @@ -82,8 +82,8 @@ typedef struct AvbOps AvbOps; /* Forward-declaration of operations in libavb_ab. */ struct AvbABOps; -/* Forward-declaration of operations in libavb_atx. */ -struct AvbAtxOps; +/* Forward-declaration of operations in libavb_cert. */ +struct AvbCertOps; /* High-level operations/functions/methods that are platform * dependent. @@ -104,10 +104,10 @@ struct AvbOps { */ struct AvbABOps* ab_ops; - /* If libavb_atx is used, this should point to the - * AvbAtxOps. Otherwise it must be set to NULL. + /* If libavb_cert is used, this should point to the + * AvbCertOps. Otherwise it must be set to NULL. */ - struct AvbAtxOps* atx_ops; + struct AvbCertOps* cert_ops; /* Reads |num_bytes| from offset |offset| from partition with name * |partition| (NUL-terminated UTF-8 string). If |offset| is diff --git a/libavb/avb_slot_verify.c b/libavb/avb_slot_verify.c index a548c80..3ff59c2 100644 --- a/libavb/avb_slot_verify.c +++ b/libavb/avb_slot_verify.c @@ -92,7 +92,7 @@ static AvbSlotVerifyResult load_full_partition(AvbOps* ops, /* We are going to implicitly cast image_size from uint64_t to size_t in the * following code, so we need to make sure that the cast is safe. */ if (image_size != (size_t)(image_size)) { - avb_errorv(part_name, ": Partition size too large to load.\n", NULL); + avb_error(part_name, ": Partition size too large to load.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; } @@ -103,14 +103,14 @@ static AvbSlotVerifyResult load_full_partition(AvbOps* ops, if (io_ret == AVB_IO_RESULT_ERROR_OOM) { return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error loading data from partition.\n", NULL); + avb_error(part_name, ": Error loading data from partition.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_IO; } if (*out_image_buf != NULL) { *out_image_preloaded = true; if (part_num_read != image_size) { - avb_errorv(part_name, ": Read incorrect number of bytes.\n", NULL); + avb_error(part_name, ": Read incorrect number of bytes.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_IO; } } @@ -132,11 +132,11 @@ static AvbSlotVerifyResult load_full_partition(AvbOps* ops, if (io_ret == AVB_IO_RESULT_ERROR_OOM) { return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error loading data from partition.\n", NULL); + avb_error(part_name, ": Error loading data from partition.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_IO; } if (part_num_read != image_size) { - avb_errorv(part_name, ": Read incorrect number of bytes.\n", NULL); + avb_error(part_name, ": Read incorrect number of bytes.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_IO; } } @@ -170,7 +170,7 @@ static AvbSlotVerifyResult read_persistent_digest(AvbOps* ops, size_t stored_digest_size = 0; if (ops->read_persistent_value == NULL) { - avb_errorv(part_name, ": Persistent values are not implemented.\n", NULL); + avb_error(part_name, ": Persistent values are not implemented.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; } persistent_value_name = @@ -207,19 +207,17 @@ static AvbSlotVerifyResult read_persistent_digest(AvbOps* ops, } else if (io_ret == AVB_IO_RESULT_ERROR_NO_SUCH_VALUE) { // Treat a missing persistent value as a verification error, which is // ignoreable, rather than a metadata error which is not. - avb_errorv(part_name, ": Persistent digest does not exist.\n", NULL); + avb_error(part_name, ": Persistent digest does not exist.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION; } else if (io_ret == AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE || io_ret == AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE) { - avb_errorv( - part_name, ": Persistent digest is not of expected size.\n", NULL); + avb_error(part_name, ": Persistent digest is not of expected size.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error reading persistent digest.\n", NULL); + avb_error(part_name, ": Error reading persistent digest.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_IO; } else if (expected_digest_size != stored_digest_size) { - avb_errorv( - part_name, ": Persistent digest is not of expected size.\n", NULL); + avb_error(part_name, ": Persistent digest is not of expected size.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; } return AVB_SLOT_VERIFY_RESULT_OK; @@ -245,23 +243,21 @@ static AvbSlotVerifyResult initialize_persistent_digest( } if (is_device_unlocked) { - avb_debugv(part_name, - ": Digest does not exist, device unlocked so not initializing " - "digest.\n", - NULL); + avb_debug(part_name, + ": Digest does not exist, device unlocked so not initializing " + "digest.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION; } // Device locked; initialize digest with given initial value. - avb_debugv(part_name, - ": Digest does not exist, initializing persistent digest.\n", - NULL); + avb_debug(part_name, + ": Digest does not exist, initializing persistent digest.\n"); io_ret = ops->write_persistent_value( ops, persistent_value_name, digest_size, initial_digest); if (io_ret == AVB_IO_RESULT_ERROR_OOM) { return AVB_SLOT_VERIFY_RESULT_ERROR_OOM; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error initializing persistent digest.\n", NULL); + avb_error(part_name, ": Error initializing persistent digest.\n"); return AVB_SLOT_VERIFY_RESULT_ERROR_IO; } @@ -271,9 +267,8 @@ static AvbSlotVerifyResult initialize_persistent_digest( // initial_digest ensures that this will not recurse again. ret = read_persistent_digest(ops, part_name, digest_size, NULL, out_digest); if (ret != AVB_SLOT_VERIFY_RESULT_OK) { - avb_errorv(part_name, - ": Reading back initialized persistent digest failed!\n", - NULL); + avb_error(part_name, + ": Reading back initialized persistent digest failed!\n"); } return ret; } @@ -376,11 +371,11 @@ static AvbSlotVerifyResult load_and_verify_hash_partition( ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto out; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error determining partition size.\n", NULL); + avb_error(part_name, ": Error determining partition size.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; goto out; } - avb_debugv(part_name, ": Loading entire partition.\n", NULL); + avb_debug(part_name, ": Loading entire partition.\n"); } ret = load_full_partition( @@ -412,14 +407,14 @@ static AvbSlotVerifyResult load_and_verify_hash_partition( digest = avb_sha512_final(&sha512_ctx); digest_len = AVB_SHA512_DIGEST_SIZE; } else { - avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL); + avb_error(part_name, ": Unsupported hash algorithm.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } if (hash_desc.digest_len == 0) { /* Expect a match to a persistent digest. */ - avb_debugv(part_name, ": No digest, using persistent digest.\n", NULL); + avb_debug(part_name, ": No digest, using persistent digest.\n"); expected_digest_len = digest_len; expected_digest = expected_digest_buf; avb_assert(expected_digest_len <= sizeof(expected_digest_buf)); @@ -438,16 +433,14 @@ static AvbSlotVerifyResult load_and_verify_hash_partition( } if (digest_len != expected_digest_len) { - avb_errorv( - part_name, ": Digest in descriptor not of expected size.\n", NULL); + avb_error(part_name, ": Digest in descriptor not of expected size.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } if (avb_safe_memcmp(digest, expected_digest, digest_len) != 0) { - avb_errorv(part_name, - ": Hash of data does not match digest in descriptor.\n", - NULL); + avb_error(part_name, + ": Hash of data does not match digest in descriptor.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION; goto out; } @@ -461,7 +454,7 @@ out: image_buf != NULL) { AvbPartitionData* loaded_partition; if (slot_data->num_loaded_partitions == MAX_NUMBER_OF_LOADED_PARTITIONS) { - avb_errorv(part_name, ": Too many loaded partitions.\n", NULL); + avb_error(part_name, ": Too many loaded partitions.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto fail; } @@ -514,11 +507,11 @@ static AvbSlotVerifyResult load_requested_partitions( ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto out; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(part_name, ": Error determining partition size.\n", NULL); + avb_error(part_name, ": Error determining partition size.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; goto out; } - avb_debugv(part_name, ": Loading entire partition.\n", NULL); + avb_debug(part_name, ": Loading entire partition.\n"); ret = load_full_partition( ops, part_name, image_size, &image_buf, &image_preloaded); @@ -528,7 +521,7 @@ static AvbSlotVerifyResult load_requested_partitions( /* Move to slot_data. */ if (slot_data->num_loaded_partitions == MAX_NUMBER_OF_LOADED_PARTITIONS) { - avb_errorv(part_name, ": Too many loaded partitions.\n", NULL); + avb_error(part_name, ": Too many loaded partitions.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto out; } @@ -572,7 +565,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( size_t expected_public_key_length, AvbSlotVerifyData* slot_data, AvbAlgorithmType* out_algorithm_type, - AvbCmdlineSubstList* out_additional_cmdline_subst) { + AvbCmdlineSubstList* out_additional_cmdline_subst, + bool use_ab_suffix) { char full_partition_name[AVB_PART_NAME_MAX_SIZE]; AvbSlotVerifyResult ret; AvbIOResult io_ret; @@ -620,17 +614,27 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } - - /* Construct full partition name e.g. system_a. */ - if (!avb_str_concat(full_partition_name, + if (!use_ab_suffix) { + /*No ab_suffix, just copy the partition name as is.*/ + if (partition_name_len >= AVB_PART_NAME_MAX_SIZE) { + avb_error("Partition name does not fit.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } + avb_memcpy(full_partition_name, partition_name, partition_name_len); + full_partition_name[partition_name_len] = '\0'; + }else{ + /* Construct full partition name e.g. system_a. */ + if (!avb_str_concat(full_partition_name, sizeof full_partition_name, partition_name, partition_name_len, ab_suffix, avb_strlen(ab_suffix))) { - avb_error("Partition name and suffix does not fit.\n"); - ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; - goto out; + avb_error("Partition name and suffix does not fit.\n"); + ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; + goto out; + } } /* If we're loading from the main vbmeta partition, the vbmeta struct is in @@ -656,7 +660,7 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto out; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(full_partition_name, ": Error loading footer.\n", NULL); + avb_error(full_partition_name, ": Error loading footer.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; goto out; } @@ -664,12 +668,11 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( if (!avb_footer_validate_and_byteswap((const AvbFooter*)footer_buf, &footer)) { - avb_debugv(full_partition_name, ": No footer detected.\n", NULL); + avb_debug(full_partition_name, ": No footer detected.\n"); } else { /* Basic footer sanity check since the data is untrusted. */ if (footer.vbmeta_size > VBMETA_MAX_SIZE) { - avb_errorv( - full_partition_name, ": Invalid vbmeta size in footer.\n", NULL); + avb_error(full_partition_name, ": Invalid vbmeta size in footer.\n"); } else { vbmeta_offset = footer.vbmeta_offset; vbmeta_size = footer.vbmeta_size; @@ -681,13 +684,12 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( ops->get_size_of_partition(ops, full_partition_name, &partition_size); if (io_ret == AVB_IO_RESULT_OK) { if (partition_size < vbmeta_size && partition_size > 0) { - avb_debugv(full_partition_name, - ": Using partition size as vbmeta size\n", - NULL); + avb_debug(full_partition_name, + ": Using partition size as vbmeta size\n"); vbmeta_size = partition_size; } } else { - avb_debugv(full_partition_name, ": Failed to get partition size\n", NULL); + avb_debug(full_partition_name, ": Failed to get partition size\n"); // libavb might fall back to other partitions if current vbmeta partition // isn't found. So AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION is recoverable, // but other errors are not. @@ -711,15 +713,13 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( } if (vbmeta_offset != 0) { - avb_debugv("Loading vbmeta struct in footer from partition '", - full_partition_name, - "'.\n", - NULL); + avb_debug("Loading vbmeta struct in footer from partition '", + full_partition_name, + "'.\n"); } else { - avb_debugv("Loading vbmeta struct from partition '", - full_partition_name, - "'.\n", - NULL); + avb_debug("Loading vbmeta struct from partition '", + full_partition_name, + "'.\n"); } io_ret = ops->read_from_partition(ops, @@ -738,9 +738,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( */ if (is_main_vbmeta && io_ret == AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION && !look_for_vbmeta_footer) { - avb_debugv(full_partition_name, - ": No such partition. Trying 'boot' instead.\n", - NULL); + avb_debug(full_partition_name, + ": No such partition. Trying 'boot' instead.\n"); ret = load_and_verify_vbmeta(ops, requested_partitions, ab_suffix, @@ -754,10 +753,11 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( 0 /* expected_public_key_length */, slot_data, out_algorithm_type, - out_additional_cmdline_subst); + out_additional_cmdline_subst, + use_ab_suffix); goto out; } else { - avb_errorv(full_partition_name, ": Error loading vbmeta data.\n", NULL); + avb_error(full_partition_name, ": Error loading vbmeta data.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; goto out; } @@ -778,11 +778,10 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( case AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH: case AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH: ret = AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION; - avb_errorv(full_partition_name, - ": Error verifying vbmeta image: ", - avb_vbmeta_verify_result_to_string(vbmeta_ret), - "\n", - NULL); + avb_error(full_partition_name, + ": Error verifying vbmeta image: ", + avb_vbmeta_verify_result_to_string(vbmeta_ret), + "\n"); if (!allow_verification_error) { goto out; } @@ -791,17 +790,15 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( case AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER: /* No way to continue this case. */ ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; - avb_errorv(full_partition_name, - ": Error verifying vbmeta image: invalid vbmeta header\n", - NULL); + avb_error(full_partition_name, + ": Error verifying vbmeta image: invalid vbmeta header\n"); goto out; case AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION: /* No way to continue this case. */ ret = AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION; - avb_errorv(full_partition_name, - ": Error verifying vbmeta image: unsupported AVB version\n", - NULL); + avb_error(full_partition_name, + ": Error verifying vbmeta image: unsupported AVB version\n"); goto out; } @@ -815,9 +812,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( } else { if (vbmeta_header.flags != 0) { ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; - avb_errorv(full_partition_name, - ": chained vbmeta image has non-zero flags\n", - NULL); + avb_error(full_partition_name, + ": chained vbmeta image has non-zero flags\n"); goto out; } } @@ -833,10 +829,9 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( avb_assert(!is_main_vbmeta); if (expected_public_key_length != pk_len || avb_safe_memcmp(expected_public_key, pk_data, pk_len) != 0) { - avb_errorv(full_partition_name, - ": Public key used to sign data does not match key in chain " - "partition descriptor.\n", - NULL); + avb_error(full_partition_name, + ": Public key used to sign data does not match key in chain " + "partition descriptor.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED; if (!allow_verification_error) { goto out; @@ -879,16 +874,14 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto out; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(full_partition_name, - ": Error while checking public key used to sign data.\n", - NULL); + avb_error(full_partition_name, + ": Error while checking public key used to sign data.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; goto out; } if (!key_is_trusted) { - avb_errorv(full_partition_name, - ": Public key used to sign data rejected.\n", - NULL); + avb_error(full_partition_name, + ": Public key used to sign data rejected.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED; if (!allow_verification_error) { goto out; @@ -904,17 +897,15 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto out; } else if (io_ret != AVB_IO_RESULT_OK) { - avb_errorv(full_partition_name, - ": Error getting rollback index for location.\n", - NULL); + avb_error(full_partition_name, + ": Error getting rollback index for location.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_IO; goto out; } if (vbmeta_header.rollback_index < stored_rollback_index) { - avb_errorv( + avb_error( full_partition_name, - ": Image rollback index is less than the stored rollback index.\n", - NULL); + ": Image rollback index is less than the stored rollback index.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX; if (!allow_verification_error) { goto out; @@ -930,7 +921,7 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( } } if (slot_data->num_vbmeta_images == MAX_NUMBER_OF_VBMETA_IMAGES) { - avb_errorv(full_partition_name, ": Too many vbmeta images.\n", NULL); + avb_error(full_partition_name, ": Too many vbmeta images.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_OOM; goto out; } @@ -954,8 +945,7 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( */ if (vbmeta_header.flags & AVB_VBMETA_IMAGE_FLAGS_VERIFICATION_DISABLED) { AvbSlotVerifyResult sub_ret; - avb_debugv( - full_partition_name, ": VERIFICATION_DISABLED bit is set.\n", NULL); + avb_debug(full_partition_name, ": VERIFICATION_DISABLED bit is set.\n"); /* If load_requested_partitions() fail it is always a fatal * failure (e.g. ERROR_INVALID_ARGUMENT, ERROR_OOM, etc.) rather * than recoverable (e.g. one where result_should_continue() @@ -988,7 +978,7 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( AvbDescriptor desc; if (!avb_descriptor_validate_and_byteswap(descriptors[n], &desc)) { - avb_errorv(full_partition_name, ": Descriptor is invalid.\n", NULL); + avb_error(full_partition_name, ": Descriptor is invalid.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } @@ -1018,31 +1008,32 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( /* Only allow CHAIN_PARTITION descriptors in the main vbmeta image. */ if (!is_main_vbmeta) { - avb_errorv(full_partition_name, - ": Encountered chain descriptor not in main image.\n", - NULL); + avb_error(full_partition_name, + ": Encountered chain descriptor not in main image.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } if (!avb_chain_partition_descriptor_validate_and_byteswap( (AvbChainPartitionDescriptor*)descriptors[n], &chain_desc)) { - avb_errorv(full_partition_name, - ": Chain partition descriptor is invalid.\n", - NULL); + avb_error(full_partition_name, + ": Chain partition descriptor is invalid.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } if (chain_desc.rollback_index_location == 0) { - avb_errorv(full_partition_name, - ": Chain partition has invalid " - "rollback_index_location field.\n", - NULL); + avb_error(full_partition_name, + ": Chain partition has invalid " + "rollback_index_location field.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } - + if ((chain_desc.flags & AVB_HASH_DESCRIPTOR_FLAGS_DO_NOT_USE_AB) != 0){ + use_ab_suffix = false; + }else{ + use_ab_suffix = true; + } chain_partition_name = ((const uint8_t*)descriptors[n]) + sizeof(AvbChainPartitionDescriptor); chain_public_key = chain_partition_name + chain_desc.partition_name_len; @@ -1061,7 +1052,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( chain_desc.public_key_len, slot_data, NULL, /* out_algorithm_type */ - NULL /* out_additional_cmdline_subst */); + NULL, /* out_additional_cmdline_subst */ + use_ab_suffix); if (sub_ret != AVB_SLOT_VERIFY_RESULT_OK) { ret = sub_ret; if (!result_should_continue(ret)) { @@ -1078,9 +1070,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( if (!avb_kernel_cmdline_descriptor_validate_and_byteswap( (AvbKernelCmdlineDescriptor*)descriptors[n], &kernel_cmdline_desc)) { - avb_errorv(full_partition_name, - ": Kernel cmdline descriptor is invalid.\n", - NULL); + avb_error(full_partition_name, + ": Kernel cmdline descriptor is invalid.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } @@ -1090,9 +1081,8 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( if (!avb_validate_utf8(kernel_cmdline, kernel_cmdline_desc.kernel_cmdline_length)) { - avb_errorv(full_partition_name, - ": Kernel cmdline is not valid UTF-8.\n", - NULL); + avb_error(full_partition_name, + ": Kernel cmdline is not valid UTF-8.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } @@ -1152,8 +1142,7 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( if (!avb_hashtree_descriptor_validate_and_byteswap( (AvbHashtreeDescriptor*)descriptors[n], &hashtree_desc)) { - avb_errorv( - full_partition_name, ": Hashtree descriptor is invalid.\n", NULL); + avb_error(full_partition_name, ": Hashtree descriptor is invalid.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } @@ -1207,7 +1196,7 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( "sha512") == 0) { digest_len = AVB_SHA512_DIGEST_SIZE; } else { - avb_errorv(part_name, ": Unsupported hash algorithm.\n", NULL); + avb_error(part_name, ": Unsupported hash algorithm.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } @@ -1242,8 +1231,7 @@ static AvbSlotVerifyResult load_and_verify_vbmeta( if (rollback_index_location_to_use >= AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS) { - avb_errorv( - full_partition_name, ": Invalid rollback_index_location.\n", NULL); + avb_error(full_partition_name, ": Invalid rollback_index_location.\n"); ret = AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA; goto out; } @@ -1508,7 +1496,8 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, 0 /* expected_public_key_length */, slot_data, &algorithm_type, - additional_cmdline_subst); + additional_cmdline_subst, + true /*use_ab_suffix*/); if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) { goto fail; } @@ -1529,7 +1518,8 @@ AvbSlotVerifyResult avb_slot_verify(AvbOps* ops, 0 /* expected_public_key_length */, slot_data, &algorithm_type, - additional_cmdline_subst); + additional_cmdline_subst, + true /*use_ab_suffix*/); if (!allow_verification_error && ret != AVB_SLOT_VERIFY_RESULT_OK) { goto fail; } diff --git a/libavb/avb_sysdeps.h b/libavb/avb_sysdeps.h index e511a8a..b4a1e99 100644 --- a/libavb/avb_sysdeps.h +++ b/libavb/avb_sysdeps.h @@ -48,6 +48,7 @@ extern "C" { */ #define AVB_ATTR_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) #define AVB_ATTR_PACKED __attribute__((packed)) +#define AVB_ATTR_PRINTF(x, y) __attribute__((format(printf, x, y))) #define AVB_ATTR_NO_RETURN __attribute__((noreturn)) #define AVB_ATTR_SENTINEL __attribute__((__sentinel__)) @@ -95,10 +96,16 @@ void* avb_memset(void* dest, const int c, size_t n); void avb_print(const char* message); /* Prints out a vector of strings. Each argument must point to a - * NUL-terminated UTF-8 string and NULL should be the last argument. + * NUL-terminated UTF-8 string and NULL must be the last argument. */ void avb_printv(const char* message, ...) AVB_ATTR_SENTINEL; +/* Prints out a formatted string. + * + * Replaces avb_printv when AVB_USE_PRINTF_LOGS is enabled. + */ +void avb_printf(const char* fmt, ...) AVB_ATTR_PRINTF(1, 2); + /* Aborts the program or reboots the device. */ void avb_abort(void) AVB_ATTR_NO_RETURN; diff --git a/libavb/avb_sysdeps_posix.c b/libavb/avb_sysdeps_posix.c index e26c3ef..30eb029 100644 --- a/libavb/avb_sysdeps_posix.c +++ b/libavb/avb_sysdeps_posix.c @@ -22,7 +22,6 @@ * SOFTWARE. */ -#include <endian.h> #include <stdarg.h> #include <stdio.h> #include <stdlib.h> @@ -58,6 +57,30 @@ void avb_abort(void) { abort(); } +static FILE* get_log_stream() { +#ifdef USE_KMSG_AS_LOG_TARGET + static FILE* fp = NULL; + + if (fp == NULL) { + fp = fopen("/dev/kmsg", "ae"); + if (fp == NULL || setvbuf(fp, NULL, _IONBF, 0) != 0) { + fp = stderr; + } + } + + return fp; +#else + return stderr; +#endif +} + +void avb_printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(get_log_stream(), fmt, ap); + va_end(ap); +} + void avb_print(const char* message) { fprintf(stderr, "%s", message); } diff --git a/libavb/avb_util.h b/libavb/avb_util.h index b6b036e..da638fc 100644 --- a/libavb/avb_util.h +++ b/libavb/avb_util.h @@ -35,9 +35,44 @@ extern "C" { #endif +#define AVB_CONCAT(x, y) x##y #define AVB_STRINGIFY(x) #x #define AVB_TO_STRING(x) AVB_STRINGIFY(x) +#define AVB__COUNT_ARGS(_0, _1, _2, _3, _4, _5, _6, _7, x, ...) x +#define AVB_COUNT_ARGS(...) \ + AVB__COUNT_ARGS(, ##__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0) + +#define AVB__REPEAT0(x) +#define AVB__REPEAT1(x) x +#define AVB__REPEAT2(x) AVB__REPEAT1(x) x +#define AVB__REPEAT3(x) AVB__REPEAT2(x) x +#define AVB__REPEAT4(x) AVB__REPEAT3(x) x +#define AVB__REPEAT5(x) AVB__REPEAT4(x) x +#define AVB__REPEAT6(x) AVB__REPEAT5(x) x +#define AVB__REPEAT7(x) AVB__REPEAT6(x) x +#define AVB__REPEAT(n, x) AVB_CONCAT(AVB__REPEAT, n)(x) +#define AVB_REPEAT(n, x) AVB__REPEAT(n, x) + +#ifdef AVB_USE_PRINTF_LOGS +#define AVB_LOG(level, message, ...) \ + avb_printf("%s:%d: " level \ + ": " AVB_REPEAT(AVB_COUNT_ARGS(message, ##__VA_ARGS__), "%s"), \ + avb_basename(__FILE__), \ + __LINE__, \ + message, \ + ##__VA_ARGS__) +#else +#define AVB_LOG(level, message, ...) \ + avb_printv(avb_basename(__FILE__), \ + ":", \ + AVB_TO_STRING(__LINE__), \ + ": " level ": ", \ + message, \ + ##__VA_ARGS__, \ + NULL) +#endif + #ifdef AVB_ENABLE_DEBUG /* Aborts the program if |expr| is false. * @@ -49,21 +84,28 @@ extern "C" { avb_fatal("assert fail: " #expr "\n"); \ } \ } while (0) -#else -#define avb_assert(expr) -#endif /* Aborts the program if reached. * * This has no effect unless AVB_ENABLE_DEBUG is defined. */ -#ifdef AVB_ENABLE_DEBUG #define avb_assert_not_reached() \ do { \ avb_fatal("assert_not_reached()\n"); \ } while (0) + +/* Print functions, used for diagnostics. + * + * These have no effect unless AVB_ENABLE_DEBUG is defined. + */ +#define avb_debug(message, ...) \ + do { \ + AVB_LOG("DEBUG", message, ##__VA_ARGS__); \ + } while (0) #else +#define avb_assert(expr) #define avb_assert_not_reached() +#define avb_debug(message, ...) #endif /* Aborts the program if |addr| is not word-aligned. @@ -73,79 +115,30 @@ extern "C" { #define avb_assert_aligned(addr) \ avb_assert((((uintptr_t)addr) & (AVB_ALIGNMENT_SIZE - 1)) == 0) -#ifdef AVB_ENABLE_DEBUG -/* Print functions, used for diagnostics. - * - * These have no effect unless AVB_ENABLE_DEBUG is defined. - */ -#define avb_debug(message) \ - do { \ - avb_printv(avb_basename(__FILE__), \ - ":", \ - AVB_TO_STRING(__LINE__), \ - ": DEBUG: ", \ - message, \ - NULL); \ - } while (0) -#define avb_debugv(message, ...) \ - do { \ - avb_printv(avb_basename(__FILE__), \ - ":", \ - AVB_TO_STRING(__LINE__), \ - ": DEBUG: ", \ - message, \ - ##__VA_ARGS__); \ - } while (0) -#else -#define avb_debug(message) -#define avb_debugv(message, ...) -#endif - /* Prints out a message. This is typically used if a runtime-error * occurs. */ -#define avb_error(message) \ - do { \ - avb_printv(avb_basename(__FILE__), \ - ":", \ - AVB_TO_STRING(__LINE__), \ - ": ERROR: ", \ - message, \ - NULL); \ - } while (0) -#define avb_errorv(message, ...) \ - do { \ - avb_printv(avb_basename(__FILE__), \ - ":", \ - AVB_TO_STRING(__LINE__), \ - ": ERROR: ", \ - message, \ - ##__VA_ARGS__); \ +#define avb_error(message, ...) \ + do { \ + AVB_LOG("ERROR", message, ##__VA_ARGS__); \ } while (0) /* Prints out a message and calls avb_abort(). */ -#define avb_fatal(message) \ - do { \ - avb_printv(avb_basename(__FILE__), \ - ":", \ - AVB_TO_STRING(__LINE__), \ - ": FATAL: ", \ - message, \ - NULL); \ - avb_abort(); \ - } while (0) -#define avb_fatalv(message, ...) \ - do { \ - avb_printv(avb_basename(__FILE__), \ - ":", \ - AVB_TO_STRING(__LINE__), \ - ": FATAL: ", \ - message, \ - ##__VA_ARGS__); \ - avb_abort(); \ +#define avb_fatal(message, ...) \ + do { \ + AVB_LOG("FATAL", message, ##__VA_ARGS__); \ + avb_abort(); \ } while (0) +#ifndef AVB_USE_PRINTF_LOGS +/* Deprecated legacy logging functions -- kept for client compatibility. + */ +#define avb_debugv(message, ...) avb_debug(message, ##__VA_ARGS__) +#define avb_errorv(message, ...) avb_error(message, ##__VA_ARGS__) +#define avb_fatalv(message, ...) avb_fatal(message, ##__VA_ARGS__) +#endif + /* Converts a 16-bit unsigned integer from big-endian to host byte order. */ uint16_t avb_be16toh(uint16_t in) AVB_ATTR_WARN_UNUSED_RESULT; diff --git a/libavb/avb_version.h b/libavb/avb_version.h index e7b7f53..171cc44 100644 --- a/libavb/avb_version.h +++ b/libavb/avb_version.h @@ -37,7 +37,7 @@ extern "C" { /* The version number of AVB - keep in sync with avbtool. */ #define AVB_VERSION_MAJOR 1 -#define AVB_VERSION_MINOR 2 +#define AVB_VERSION_MINOR 3 #define AVB_VERSION_SUB 0 /* Returns a NUL-terminated string for the libavb version in use. The diff --git a/libavb_ab/avb_ab_flow.c b/libavb_ab/avb_ab_flow.c index bf6eab1..cbdf6c9 100644 --- a/libavb_ab/avb_ab_flow.c +++ b/libavb_ab/avb_ab_flow.c @@ -263,15 +263,12 @@ AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops, case AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED: if (flags & AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR) { /* Do nothing since we allow this. */ - avb_debugv("Allowing slot ", - slot_suffixes[n], - " which verified " - "with result ", - avb_slot_verify_result_to_string(verify_result), - " because " - "AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR " - "is set.\n", - NULL); + avb_debug("Allowing slot ", + slot_suffixes[n], + " which verified with result ", + avb_slot_verify_result_to_string(verify_result), + " because AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR " + "is set.\n"); saw_and_allowed_verification_error = true; } else { set_slot_unbootable = true; @@ -285,12 +282,11 @@ AvbABFlowResult avb_ab_flow(AvbABOps* ab_ops, } if (set_slot_unbootable) { - avb_errorv("Error verifying slot ", - slot_suffixes[n], - " with result ", - avb_slot_verify_result_to_string(verify_result), - " - setting unbootable.\n", - NULL); + avb_error("Error verifying slot ", + slot_suffixes[n], + " with result ", + avb_slot_verify_result_to_string(verify_result), + " - setting unbootable.\n"); slot_set_unbootable(&ab_data.slots[n]); } } diff --git a/libavb_atx/avb_atx_types.h b/libavb_atx/avb_atx_types.h deleted file mode 100644 index e78bbfa..0000000 --- a/libavb_atx/avb_atx_types.h +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright (C) 2016 The Android Open Source Project - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, copy, - * modify, merge, publish, distribute, sublicense, and/or sell copies - * of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF - * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS - * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN - * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN - * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ - -#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION) -#error \ - "Never include this file directly, include libavb_atx/libavb_atx.h instead." -#endif - -#ifndef AVB_ATX_TYPES_H_ -#define AVB_ATX_TYPES_H_ - -#include <libavb/libavb.h> - -#ifdef __cplusplus -extern "C" { -#endif - -/* Size in bytes of an Android Things product ID. */ -#define AVB_ATX_PRODUCT_ID_SIZE 16 - -/* Size in bytes of an Android Things unlock challenge. */ -#define AVB_ATX_UNLOCK_CHALLENGE_SIZE 16 - -/* Size in bytes of a serialized public key with a 4096-bit modulus. */ -#define AVB_ATX_PUBLIC_KEY_SIZE (sizeof(AvbRSAPublicKeyHeader) + 1024) - -/* Data structure of Android Things permanent attributes. */ -typedef struct AvbAtxPermanentAttributes { - uint32_t version; - uint8_t product_root_public_key[AVB_ATX_PUBLIC_KEY_SIZE]; - uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]; -} AVB_ATTR_PACKED AvbAtxPermanentAttributes; - -/* Data structure of signed fields in an Android Things certificate. */ -typedef struct AvbAtxCertificateSignedData { - uint32_t version; - uint8_t public_key[AVB_ATX_PUBLIC_KEY_SIZE]; - uint8_t subject[AVB_SHA256_DIGEST_SIZE]; - uint8_t usage[AVB_SHA256_DIGEST_SIZE]; - uint64_t key_version; -} AVB_ATTR_PACKED AvbAtxCertificateSignedData; - -/* Data structure of an Android Things certificate. */ -typedef struct AvbAtxCertificate { - AvbAtxCertificateSignedData signed_data; - uint8_t signature[AVB_RSA4096_NUM_BYTES]; -} AVB_ATTR_PACKED AvbAtxCertificate; - -/* Data structure of Android Things public key metadata in vbmeta. */ -typedef struct AvbAtxPublicKeyMetadata { - uint32_t version; - AvbAtxCertificate product_intermediate_key_certificate; - AvbAtxCertificate product_signing_key_certificate; -} AVB_ATTR_PACKED AvbAtxPublicKeyMetadata; - -/* Data structure of an Android Things unlock challenge. */ -typedef struct AvbAtxUnlockChallenge { - uint32_t version; - uint8_t product_id_hash[AVB_SHA256_DIGEST_SIZE]; - uint8_t challenge[AVB_ATX_UNLOCK_CHALLENGE_SIZE]; -} AVB_ATTR_PACKED AvbAtxUnlockChallenge; - -/* Data structure of an Android Things unlock credential. */ -typedef struct AvbAtxUnlockCredential { - uint32_t version; - AvbAtxCertificate product_intermediate_key_certificate; - AvbAtxCertificate product_unlock_key_certificate; - uint8_t challenge_signature[AVB_RSA4096_NUM_BYTES]; -} AVB_ATTR_PACKED AvbAtxUnlockCredential; - -#ifdef __cplusplus -} -#endif - -#endif /* AVB_ATX_TYPES_H_ */ diff --git a/libavb_atx/avb_atx_ops.h b/libavb_cert/avb_cert_ops.h index 53c898d..2c1f6a8 100644 --- a/libavb_atx/avb_atx_ops.h +++ b/libavb_cert/avb_cert_ops.h @@ -22,27 +22,27 @@ * SOFTWARE. */ -#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION) +#if !defined(AVB_INSIDE_LIBAVB_CERT_H) && !defined(AVB_COMPILATION) #error \ - "Never include this file directly, include libavb_atx/libavb_atx.h instead." + "Never include this file directly, include libavb_cert/libavb_cert.h instead." #endif -#ifndef AVB_ATX_OPS_H_ -#define AVB_ATX_OPS_H_ +#ifndef AVB_CERT_OPS_H_ +#define AVB_CERT_OPS_H_ #include <libavb/libavb.h> -#include "avb_atx_types.h" +#include "avb_cert_types.h" #ifdef __cplusplus extern "C" { #endif -struct AvbAtxOps; -typedef struct AvbAtxOps AvbAtxOps; +struct AvbCertOps; +typedef struct AvbCertOps AvbCertOps; -/* An extension to AvbOps required by avb_atx_validate_vbmeta_public_key(). */ -struct AvbAtxOps { +/* An extension to AvbOps required by avb_cert_validate_vbmeta_public_key(). */ +struct AvbCertOps { /* Operations from libavb. */ AvbOps* ops; @@ -51,19 +51,19 @@ struct AvbAtxOps { * |attributes|. */ AvbIOResult (*read_permanent_attributes)( - AvbAtxOps* atx_ops, AvbAtxPermanentAttributes* attributes); + AvbCertOps* cert_ops, AvbCertPermanentAttributes* attributes); /* Reads a |hash| of permanent attributes. This hash MUST be retrieved from a * permanently read-only location (e.g. fuses) when a device is LOCKED. On * success, returned AVB_IO_RESULT_OK and populates |hash|. */ AvbIOResult (*read_permanent_attributes_hash)( - AvbAtxOps* atx_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]); + AvbCertOps* cert_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]); /* Provides the key version of a key used during verification. This may be * useful for managing the minimum key version. */ - void (*set_key_version)(AvbAtxOps* atx_ops, + void (*set_key_version)(AvbCertOps* cert_ops, size_t rollback_index_location, uint64_t key_version); @@ -72,7 +72,7 @@ struct AvbAtxOps { * * Returns AVB_IO_RESULT_OK on success, otherwise an error code. */ - AvbIOResult (*get_random)(AvbAtxOps* atx_ops, + AvbIOResult (*get_random)(AvbCertOps* cert_ops, size_t num_bytes, uint8_t* output); }; @@ -81,4 +81,4 @@ struct AvbAtxOps { } #endif -#endif /* AVB_ATX_OPS_H_ */ +#endif /* AVB_CERT_OPS_H_ */ diff --git a/libavb_cert/avb_cert_types.h b/libavb_cert/avb_cert_types.h new file mode 100644 index 0000000..14975ee --- /dev/null +++ b/libavb_cert/avb_cert_types.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2016 The Android Open Source Project + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, copy, + * modify, merge, publish, distribute, sublicense, and/or sell copies + * of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS + * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN + * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#if !defined(AVB_INSIDE_LIBAVB_CERT_H) && !defined(AVB_COMPILATION) +#error \ + "Never include this file directly, include libavb_cert/libavb_cert.h instead." +#endif + +#ifndef AVB_CERT_TYPES_H_ +#define AVB_CERT_TYPES_H_ + +#include <libavb/libavb.h> + +#ifdef __cplusplus +extern "C" { +#endif + +/* Size in bytes of a libavb_cert product ID. */ +#define AVB_CERT_PRODUCT_ID_SIZE 16 + +/* Size in bytes of a libavb_cert unlock challenge. */ +#define AVB_CERT_UNLOCK_CHALLENGE_SIZE 16 + +/* Size in bytes of a serialized public key with a 4096-bit modulus. */ +#define AVB_CERT_PUBLIC_KEY_SIZE (sizeof(AvbRSAPublicKeyHeader) + 1024) + +/* Data structure of libavb_cert permanent attributes. */ +typedef struct AvbCertPermanentAttributes { + uint32_t version; + uint8_t product_root_public_key[AVB_CERT_PUBLIC_KEY_SIZE]; + uint8_t product_id[AVB_CERT_PRODUCT_ID_SIZE]; +} AVB_ATTR_PACKED AvbCertPermanentAttributes; + +/* Data structure of signed fields in a libavb_cert certificate. */ +typedef struct AvbCertCertificateSignedData { + uint32_t version; + uint8_t public_key[AVB_CERT_PUBLIC_KEY_SIZE]; + uint8_t subject[AVB_SHA256_DIGEST_SIZE]; + uint8_t usage[AVB_SHA256_DIGEST_SIZE]; + uint64_t key_version; +} AVB_ATTR_PACKED AvbCertCertificateSignedData; + +/* Data structure of a libavb_cert certificate. */ +typedef struct AvbCertCertificate { + AvbCertCertificateSignedData signed_data; + uint8_t signature[AVB_RSA4096_NUM_BYTES]; +} AVB_ATTR_PACKED AvbCertCertificate; + +/* Data structure of the libavb_cert public key metadata in vbmeta. */ +typedef struct AvbCertPublicKeyMetadata { + uint32_t version; + AvbCertCertificate product_intermediate_key_certificate; + AvbCertCertificate product_signing_key_certificate; +} AVB_ATTR_PACKED AvbCertPublicKeyMetadata; + +/* Data structure of a libavb_cert unlock challenge. */ +typedef struct AvbCertUnlockChallenge { + uint32_t version; + uint8_t product_id_hash[AVB_SHA256_DIGEST_SIZE]; + uint8_t challenge[AVB_CERT_UNLOCK_CHALLENGE_SIZE]; +} AVB_ATTR_PACKED AvbCertUnlockChallenge; + +/* Data structure of a libavb_cert unlock credential. */ +typedef struct AvbCertUnlockCredential { + uint32_t version; + AvbCertCertificate product_intermediate_key_certificate; + AvbCertCertificate product_unlock_key_certificate; + uint8_t challenge_signature[AVB_RSA4096_NUM_BYTES]; +} AVB_ATTR_PACKED AvbCertUnlockCredential; + +#ifdef __cplusplus +} +#endif + +#endif /* AVB_CERT_TYPES_H_ */ diff --git a/libavb_atx/avb_atx_validate.c b/libavb_cert/avb_cert_validate.c index 90e8990..490248c 100644 --- a/libavb_atx/avb_atx_validate.c +++ b/libavb_cert/avb_cert_validate.c @@ -22,15 +22,33 @@ * SOFTWARE. */ -#include "avb_atx_validate.h" +#include "avb_cert_validate.h" #include <libavb/avb_rsa.h> #include <libavb/avb_sha.h> #include <libavb/avb_sysdeps.h> #include <libavb/avb_util.h> +/* Pre-computed SHA256 hashes for the known usage strings. + * Usage strings must match certs generated by avbtool.py. */ +/* com.google.android.things.vboot */ +const uint8_t CERT_USAGE_HASH_SIGNING[AVB_SHA256_DIGEST_SIZE] = { + 0x75, 0x04, 0x7f, 0xe1, 0x5e, 0xd4, 0x99, 0x80, 0x2d, 0xfd, 0x77, + 0x26, 0x00, 0x61, 0x18, 0xef, 0x5b, 0x06, 0x58, 0x56, 0xf5, 0x9c, + 0xa7, 0xf4, 0xdc, 0x63, 0xe7, 0x59, 0xe6, 0x48, 0xf8, 0x16}; +/* com.google.android.things.vboot.ca */ +const uint8_t CERT_USAGE_HASH_INTERMEDIATE_AUTHORITY[AVB_SHA256_DIGEST_SIZE] = { + 0x04, 0xec, 0x7c, 0xc7, 0x42, 0x41, 0x76, 0x3b, 0xcc, 0x72, 0xe3, + 0x5e, 0xd3, 0x92, 0xdf, 0xd8, 0x2a, 0x6c, 0x51, 0xae, 0xa8, 0xec, + 0x6d, 0x43, 0x27, 0xc7, 0x0d, 0xf4, 0x53, 0x4b, 0x21, 0x5c}; +/* com.google.android.things.vboot.unlock */ +const uint8_t CERT_USAGE_HASH_UNLOCK[AVB_SHA256_DIGEST_SIZE] = { + 0x7b, 0x84, 0x6c, 0x4a, 0xfd, 0x85, 0x48, 0x8f, 0x42, 0x9b, 0x7a, + 0xcf, 0x93, 0xcf, 0x6a, 0xff, 0x5c, 0x50, 0x28, 0x1b, 0xbf, 0x9b, + 0xd7, 0xb0, 0x18, 0xa5, 0x24, 0x2a, 0x86, 0x0d, 0xe3, 0xf8}; + /* The most recent unlock challenge generated. */ -static uint8_t last_unlock_challenge[AVB_ATX_UNLOCK_CHALLENGE_SIZE]; +static uint8_t last_unlock_challenge[AVB_CERT_UNLOCK_CHALLENGE_SIZE]; static bool last_unlock_challenge_set = false; /* Computes the SHA256 |hash| of |length| bytes of |data|. */ @@ -55,14 +73,9 @@ static void sha512(const uint8_t* data, avb_memcpy(hash, tmp, AVB_SHA512_DIGEST_SIZE); } -/* Computes the SHA256 |hash| of a NUL-terminated |str|. */ -static void sha256_str(const char* str, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) { - sha256((const uint8_t*)str, avb_strlen(str), hash); -} - /* Verifies structure and |expected_hash| of permanent |attributes|. */ static bool verify_permanent_attributes( - const AvbAtxPermanentAttributes* attributes, + const AvbCertPermanentAttributes* attributes, const uint8_t expected_hash[AVB_SHA256_DIGEST_SIZE]) { uint8_t hash[AVB_SHA256_DIGEST_SIZE]; @@ -70,7 +83,7 @@ static bool verify_permanent_attributes( avb_error("Unsupported permanent attributes version.\n"); return false; } - sha256((const uint8_t*)attributes, sizeof(AvbAtxPermanentAttributes), hash); + sha256((const uint8_t*)attributes, sizeof(AvbCertPermanentAttributes), hash); if (0 != avb_safe_memcmp(hash, expected_hash, AVB_SHA256_DIGEST_SIZE)) { avb_error("Invalid permanent attributes.\n"); return false; @@ -80,8 +93,8 @@ static bool verify_permanent_attributes( /* Verifies the format, key version, usage, and signature of a certificate. */ static bool verify_certificate( - const AvbAtxCertificate* certificate, - const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + const AvbCertCertificate* certificate, + const uint8_t authority[AVB_CERT_PUBLIC_KEY_SIZE], uint64_t minimum_key_version, const uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]) { const AvbAlgorithmData* algorithm_data; @@ -93,10 +106,10 @@ static bool verify_certificate( } algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA512_RSA4096); sha512((const uint8_t*)&certificate->signed_data, - sizeof(AvbAtxCertificateSignedData), + sizeof(AvbCertCertificateSignedData), certificate_hash); if (!avb_rsa_verify(authority, - AVB_ATX_PUBLIC_KEY_SIZE, + AVB_CERT_PUBLIC_KEY_SIZE, certificate->signature, AVB_RSA4096_NUM_BYTES, certificate_hash, @@ -121,14 +134,13 @@ static bool verify_certificate( /* Verifies signature and fields of a PIK certificate. */ static bool verify_pik_certificate( - const AvbAtxCertificate* certificate, - const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + const AvbCertCertificate* certificate, + const uint8_t authority[AVB_CERT_PUBLIC_KEY_SIZE], uint64_t minimum_version) { - uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]; - - sha256_str("com.google.android.things.vboot.ca", expected_usage); - if (!verify_certificate( - certificate, authority, minimum_version, expected_usage)) { + if (!verify_certificate(certificate, + authority, + minimum_version, + CERT_USAGE_HASH_INTERMEDIATE_AUTHORITY)) { avb_error("Invalid PIK certificate.\n"); return false; } @@ -137,20 +149,18 @@ static bool verify_pik_certificate( /* Verifies signature and fields of a PSK certificate. */ static bool verify_psk_certificate( - const AvbAtxCertificate* certificate, - const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + const AvbCertCertificate* certificate, + const uint8_t authority[AVB_CERT_PUBLIC_KEY_SIZE], uint64_t minimum_version, - const uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) { + const uint8_t product_id[AVB_CERT_PRODUCT_ID_SIZE]) { uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE]; - uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]; - sha256_str("com.google.android.things.vboot", expected_usage); if (!verify_certificate( - certificate, authority, minimum_version, expected_usage)) { + certificate, authority, minimum_version, CERT_USAGE_HASH_SIGNING)) { avb_error("Invalid PSK certificate.\n"); return false; } - sha256(product_id, AVB_ATX_PRODUCT_ID_SIZE, expected_subject); + sha256(product_id, AVB_CERT_PRODUCT_ID_SIZE, expected_subject); if (0 != avb_safe_memcmp(certificate->signed_data.subject, expected_subject, AVB_SHA256_DIGEST_SIZE)) { @@ -162,20 +172,18 @@ static bool verify_psk_certificate( /* Verifies signature and fields of a PUK certificate. */ static bool verify_puk_certificate( - const AvbAtxCertificate* certificate, - const uint8_t authority[AVB_ATX_PUBLIC_KEY_SIZE], + const AvbCertCertificate* certificate, + const uint8_t authority[AVB_CERT_PUBLIC_KEY_SIZE], uint64_t minimum_version, - const uint8_t product_id[AVB_ATX_PRODUCT_ID_SIZE]) { + const uint8_t product_id[AVB_CERT_PRODUCT_ID_SIZE]) { uint8_t expected_subject[AVB_SHA256_DIGEST_SIZE]; - uint8_t expected_usage[AVB_SHA256_DIGEST_SIZE]; - sha256_str("com.google.android.things.vboot.unlock", expected_usage); if (!verify_certificate( - certificate, authority, minimum_version, expected_usage)) { + certificate, authority, minimum_version, CERT_USAGE_HASH_UNLOCK)) { avb_error("Invalid PUK certificate.\n"); return false; } - sha256(product_id, AVB_ATX_PRODUCT_ID_SIZE, expected_subject); + sha256(product_id, AVB_CERT_PRODUCT_ID_SIZE, expected_subject); if (0 != avb_safe_memcmp(certificate->signed_data.subject, expected_subject, AVB_SHA256_DIGEST_SIZE)) { @@ -185,7 +193,7 @@ static bool verify_puk_certificate( return true; } -AvbIOResult avb_atx_validate_vbmeta_public_key( +AvbIOResult avb_cert_validate_vbmeta_public_key( AvbOps* ops, const uint8_t* public_key_data, size_t public_key_length, @@ -193,9 +201,9 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( size_t public_key_metadata_length, bool* out_is_trusted) { AvbIOResult result = AVB_IO_RESULT_OK; - AvbAtxPermanentAttributes permanent_attributes; + AvbCertPermanentAttributes permanent_attributes; uint8_t permanent_attributes_hash[AVB_SHA256_DIGEST_SIZE]; - AvbAtxPublicKeyMetadata metadata; + AvbCertPublicKeyMetadata metadata; uint64_t minimum_version; /* Be pessimistic so we can exit early without having to remember to clear. @@ -203,14 +211,14 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( *out_is_trusted = false; /* Read and verify permanent attributes. */ - result = ops->atx_ops->read_permanent_attributes(ops->atx_ops, - &permanent_attributes); + result = ops->cert_ops->read_permanent_attributes(ops->cert_ops, + &permanent_attributes); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read permanent attributes.\n"); return result; } - result = ops->atx_ops->read_permanent_attributes_hash( - ops->atx_ops, permanent_attributes_hash); + result = ops->cert_ops->read_permanent_attributes_hash( + ops->cert_ops, permanent_attributes_hash); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read permanent attributes hash.\n"); return result; @@ -221,11 +229,11 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( } /* Sanity check public key metadata. */ - if (public_key_metadata_length != sizeof(AvbAtxPublicKeyMetadata)) { + if (public_key_metadata_length != sizeof(AvbCertPublicKeyMetadata)) { avb_error("Invalid public key metadata.\n"); return AVB_IO_RESULT_OK; } - avb_memcpy(&metadata, public_key_metadata, sizeof(AvbAtxPublicKeyMetadata)); + avb_memcpy(&metadata, public_key_metadata, sizeof(AvbCertPublicKeyMetadata)); if (metadata.version != 1) { avb_error("Unsupported public key metadata.\n"); return AVB_IO_RESULT_OK; @@ -233,7 +241,7 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( /* Verify the PIK certificate. */ result = ops->read_rollback_index( - ops, AVB_ATX_PIK_VERSION_LOCATION, &minimum_version); + ops, AVB_CERT_PIK_VERSION_LOCATION, &minimum_version); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read PIK minimum version.\n"); return result; @@ -246,7 +254,7 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( /* Verify the PSK certificate. */ result = ops->read_rollback_index( - ops, AVB_ATX_PSK_VERSION_LOCATION, &minimum_version); + ops, AVB_CERT_PSK_VERSION_LOCATION, &minimum_version); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read PSK minimum version.\n"); return result; @@ -260,45 +268,45 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( } /* Verify the PSK is the same key that verified vbmeta. */ - if (public_key_length != AVB_ATX_PUBLIC_KEY_SIZE) { + if (public_key_length != AVB_CERT_PUBLIC_KEY_SIZE) { avb_error("Public key length mismatch.\n"); return AVB_IO_RESULT_OK; } if (0 != avb_safe_memcmp( metadata.product_signing_key_certificate.signed_data.public_key, public_key_data, - AVB_ATX_PUBLIC_KEY_SIZE)) { + AVB_CERT_PUBLIC_KEY_SIZE)) { avb_error("Public key mismatch.\n"); return AVB_IO_RESULT_OK; } /* Report the key versions used during verification. */ - ops->atx_ops->set_key_version( - ops->atx_ops, - AVB_ATX_PIK_VERSION_LOCATION, + ops->cert_ops->set_key_version( + ops->cert_ops, + AVB_CERT_PIK_VERSION_LOCATION, metadata.product_intermediate_key_certificate.signed_data.key_version); - ops->atx_ops->set_key_version( - ops->atx_ops, - AVB_ATX_PSK_VERSION_LOCATION, + ops->cert_ops->set_key_version( + ops->cert_ops, + AVB_CERT_PSK_VERSION_LOCATION, metadata.product_signing_key_certificate.signed_data.key_version); *out_is_trusted = true; return AVB_IO_RESULT_OK; } -AvbIOResult avb_atx_generate_unlock_challenge( - AvbAtxOps* atx_ops, AvbAtxUnlockChallenge* out_unlock_challenge) { +AvbIOResult avb_cert_generate_unlock_challenge( + AvbCertOps* cert_ops, AvbCertUnlockChallenge* out_unlock_challenge) { AvbIOResult result = AVB_IO_RESULT_OK; - AvbAtxPermanentAttributes permanent_attributes; + AvbCertPermanentAttributes permanent_attributes; /* We need the permanent attributes to compute the product_id_hash. */ - result = atx_ops->read_permanent_attributes(atx_ops, &permanent_attributes); + result = cert_ops->read_permanent_attributes(cert_ops, &permanent_attributes); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read permanent attributes.\n"); return result; } - result = atx_ops->get_random( - atx_ops, AVB_ATX_UNLOCK_CHALLENGE_SIZE, last_unlock_challenge); + result = cert_ops->get_random( + cert_ops, AVB_CERT_UNLOCK_CHALLENGE_SIZE, last_unlock_challenge); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to generate random challenge.\n"); return result; @@ -306,20 +314,20 @@ AvbIOResult avb_atx_generate_unlock_challenge( last_unlock_challenge_set = true; out_unlock_challenge->version = 1; sha256(permanent_attributes.product_id, - AVB_ATX_PRODUCT_ID_SIZE, + AVB_CERT_PRODUCT_ID_SIZE, out_unlock_challenge->product_id_hash); avb_memcpy(out_unlock_challenge->challenge, last_unlock_challenge, - AVB_ATX_UNLOCK_CHALLENGE_SIZE); + AVB_CERT_UNLOCK_CHALLENGE_SIZE); return result; } -AvbIOResult avb_atx_validate_unlock_credential( - AvbAtxOps* atx_ops, - const AvbAtxUnlockCredential* unlock_credential, +AvbIOResult avb_cert_validate_unlock_credential( + AvbCertOps* cert_ops, + const AvbCertUnlockCredential* unlock_credential, bool* out_is_trusted) { AvbIOResult result = AVB_IO_RESULT_OK; - AvbAtxPermanentAttributes permanent_attributes; + AvbCertPermanentAttributes permanent_attributes; uint8_t permanent_attributes_hash[AVB_SHA256_DIGEST_SIZE]; uint64_t minimum_version; const AvbAlgorithmData* algorithm_data; @@ -336,13 +344,13 @@ AvbIOResult avb_atx_validate_unlock_credential( } /* Read and verify permanent attributes. */ - result = atx_ops->read_permanent_attributes(atx_ops, &permanent_attributes); + result = cert_ops->read_permanent_attributes(cert_ops, &permanent_attributes); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read permanent attributes.\n"); return result; } - result = atx_ops->read_permanent_attributes_hash(atx_ops, - permanent_attributes_hash); + result = cert_ops->read_permanent_attributes_hash(cert_ops, + permanent_attributes_hash); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read permanent attributes hash.\n"); return result; @@ -353,8 +361,8 @@ AvbIOResult avb_atx_validate_unlock_credential( } /* Verify the PIK certificate. */ - result = atx_ops->ops->read_rollback_index( - atx_ops->ops, AVB_ATX_PIK_VERSION_LOCATION, &minimum_version); + result = cert_ops->ops->read_rollback_index( + cert_ops->ops, AVB_CERT_PIK_VERSION_LOCATION, &minimum_version); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read PIK minimum version.\n"); return result; @@ -367,8 +375,8 @@ AvbIOResult avb_atx_validate_unlock_credential( } /* Verify the PUK certificate. The minimum version is shared with the PSK. */ - result = atx_ops->ops->read_rollback_index( - atx_ops->ops, AVB_ATX_PSK_VERSION_LOCATION, &minimum_version); + result = cert_ops->ops->read_rollback_index( + cert_ops->ops, AVB_CERT_PSK_VERSION_LOCATION, &minimum_version); if (result != AVB_IO_RESULT_OK) { avb_error("Failed to read PSK minimum version.\n"); return result; @@ -387,14 +395,14 @@ AvbIOResult avb_atx_validate_unlock_credential( avb_error("Challenge does not exist.\n"); return AVB_IO_RESULT_OK; } - sha512(last_unlock_challenge, AVB_ATX_UNLOCK_CHALLENGE_SIZE, challenge_hash); + sha512(last_unlock_challenge, AVB_CERT_UNLOCK_CHALLENGE_SIZE, challenge_hash); last_unlock_challenge_set = false; /* Verify the challenge signature. */ algorithm_data = avb_get_algorithm_data(AVB_ALGORITHM_TYPE_SHA512_RSA4096); if (!avb_rsa_verify(unlock_credential->product_unlock_key_certificate .signed_data.public_key, - AVB_ATX_PUBLIC_KEY_SIZE, + AVB_CERT_PUBLIC_KEY_SIZE, unlock_credential->challenge_signature, AVB_RSA4096_NUM_BYTES, challenge_hash, diff --git a/libavb_atx/avb_atx_validate.h b/libavb_cert/avb_cert_validate.h index 1a0690d..009f2c9 100644 --- a/libavb_atx/avb_atx_validate.h +++ b/libavb_cert/avb_cert_validate.h @@ -22,47 +22,46 @@ * SOFTWARE. */ -#if !defined(AVB_INSIDE_LIBAVB_ATX_H) && !defined(AVB_COMPILATION) +#if !defined(AVB_INSIDE_LIBAVB_CERT_H) && !defined(AVB_COMPILATION) #error \ - "Never include this file directly, include libavb_atx/libavb_atx.h instead." + "Never include this file directly, include libavb_cert/libavb_cert.h instead." #endif -#ifndef AVB_ATX_VALIDATE_H_ -#define AVB_ATX_VALIDATE_H_ +#ifndef AVB_CERT_VALIDATE_H_ +#define AVB_CERT_VALIDATE_H_ -#include "avb_atx_ops.h" -#include "avb_atx_types.h" +#include "avb_cert_ops.h" +#include "avb_cert_types.h" #ifdef __cplusplus extern "C" { #endif -/* Rollback index locations for Android Things key versions. */ -#define AVB_ATX_PIK_VERSION_LOCATION 0x1000 -#define AVB_ATX_PSK_VERSION_LOCATION 0x1001 +/* Rollback index locations for libavb_cert key versions. */ +#define AVB_CERT_PIK_VERSION_LOCATION 0x1000 +#define AVB_CERT_PSK_VERSION_LOCATION 0x1001 -/* An implementation of validate_vbmeta_public_key for Android Things. See +/* An implementation of `validate_vbmeta_public_key()` for libavb_cert. See * libavb/avb_ops.h for details on validate_vbmeta_public_key in general. This - * implementation uses the metadata expected with Android Things vbmeta images - * to perform validation on the public key. The ATX ops must be implemented. - * That is, |ops->atx_ops| must be valid. + * implementation uses the metadata expected with libavb_cert vbmeta images + * to perform validation on the public key. The cert ops must be implemented. + * That is, |ops->cert_ops| must be valid. * * There are a multiple values that need verification: * - Permanent Product Attributes: A hash of these attributes is fused into * hardware. Consistency is checked. * - Product Root Key (PRK): This key is provided in permanent attributes and - * is the root authority for all Android Things - * products. + * is the root authority. * - Product Intermediate Key (PIK): This key is a rotated intermediary. It is * certified by the PRK. * - Product Signing Key (PSK): This key is a rotated authority for a specific - * Android Things product. It is certified by a - * PIK and must match |public_key_data|. + * product. It is certified by a PIK and must + * match |public_key_data|. * - Product ID: This value is provided in permanent attributes and is unique - * to a specific Android Things product. This value must match - * the subject of the PSK certificate. + * to a specific product. This value must match the subject of + * the PSK certificate. */ -AvbIOResult avb_atx_validate_vbmeta_public_key( +AvbIOResult avb_cert_validate_vbmeta_public_key( AvbOps* ops, const uint8_t* public_key_data, size_t public_key_length, @@ -71,21 +70,21 @@ AvbIOResult avb_atx_validate_vbmeta_public_key( bool* out_is_trusted); /* Generates a challenge which can be used to create an unlock credential. */ -AvbIOResult avb_atx_generate_unlock_challenge( - AvbAtxOps* atx_ops, AvbAtxUnlockChallenge* out_unlock_challenge); +AvbIOResult avb_cert_generate_unlock_challenge( + AvbCertOps* cert_ops, AvbCertUnlockChallenge* out_unlock_challenge); /* Validates an unlock credential. The certificate validation is very similar to * the validation of public key metadata except in place of the PSK is a Product * Unlock Key (PUK) and the certificate usage field identifies it as such. The * challenge signature field is verified against this PUK. */ -AvbIOResult avb_atx_validate_unlock_credential( - AvbAtxOps* atx_ops, - const AvbAtxUnlockCredential* unlock_credential, +AvbIOResult avb_cert_validate_unlock_credential( + AvbCertOps* cert_ops, + const AvbCertUnlockCredential* unlock_credential, bool* out_is_trusted); #ifdef __cplusplus } #endif -#endif /* AVB_ATX_VALIDATE_H_ */ +#endif /* AVB_CERT_VALIDATE_H_ */ diff --git a/libavb_atx/libavb_atx.h b/libavb_cert/libavb_cert.h index 839c0af..dced5ac 100644 --- a/libavb_atx/libavb_atx.h +++ b/libavb_cert/libavb_cert.h @@ -22,20 +22,20 @@ * SOFTWARE. */ -#ifndef LIBAVB_ATX_H_ -#define LIBAVB_ATX_H_ +#ifndef LIBAVB_CERT_H_ +#define LIBAVB_CERT_H_ #include <libavb/libavb.h> -/* The AVB_INSIDE_LIBAVB_ATX_H preprocessor symbol is used to enforce +/* The AVB_INSIDE_LIBAVB_CERT_H preprocessor symbol is used to enforce * library users to include only this file. All public interfaces, and * only public interfaces, must be included here. */ -#define AVB_INSIDE_LIBAVB_ATX_H -#include "avb_atx_ops.h" -#include "avb_atx_types.h" -#include "avb_atx_validate.h" -#undef AVB_INSIDE_LIBAVB_ATX_H +#define AVB_INSIDE_LIBAVB_CERT_H +#include "avb_cert_ops.h" +#include "avb_cert_types.h" +#include "avb_cert_validate.h" +#undef AVB_INSIDE_LIBAVB_CERT_H -#endif /* LIBAVB_ATX_H_ */ +#endif /* LIBAVB_CERT_H_ */ diff --git a/libavb_user/avb_ops_user.cpp b/libavb_user/avb_ops_user.cpp index d7815f0..451dff2 100644 --- a/libavb_user/avb_ops_user.cpp +++ b/libavb_user/avb_ops_user.cpp @@ -138,8 +138,7 @@ static AvbIOResult read_from_partition(AvbOps* ops, if (offset < 0) { uint64_t partition_size; if (ioctl(fd, BLKGETSIZE64, &partition_size) != 0) { - avb_errorv( - "Error getting size of \"", partition, "\" partition.\n", NULL); + avb_error("Error getting size of \"", partition, "\" partition.\n"); ret = AVB_IO_RESULT_ERROR_IO; goto out; } @@ -194,7 +193,7 @@ static AvbIOResult write_to_partition(AvbOps* ops, fd = open_partition(partition, O_WRONLY); if (fd == -1) { - avb_errorv("Error opening \"", partition, "\" partition.\n", NULL); + avb_error("Error opening \"", partition, "\" partition.\n"); ret = AVB_IO_RESULT_ERROR_IO; goto out; } @@ -271,17 +270,16 @@ static AvbIOResult get_size_of_partition(AvbOps* ops, int fd; AvbIOResult ret; - fd = open_partition(partition, O_WRONLY); + fd = open_partition(partition, O_RDONLY); if (fd == -1) { - avb_errorv("Error opening \"", partition, "\" partition.\n", NULL); + avb_error("Error opening \"", partition, "\" partition.\n"); ret = AVB_IO_RESULT_ERROR_IO; goto out; } if (out_size_in_bytes != NULL) { if (ioctl(fd, BLKGETSIZE64, out_size_in_bytes) != 0) { - avb_errorv( - "Error getting size of \"", partition, "\" partition.\n", NULL); + avb_error("Error getting size of \"", partition, "\" partition.\n"); ret = AVB_IO_RESULT_ERROR_IO; goto out; } diff --git a/libavb_user/avb_user_verification.c b/libavb_user/avb_user_verification.c index f572128..7a124d8 100644 --- a/libavb_user/avb_user_verification.c +++ b/libavb_user/avb_user_verification.c @@ -86,18 +86,15 @@ static bool load_top_level_vbmeta_header( &footer, &num_read); if (io_res != AVB_IO_RESULT_OK) { - avb_errorv("Error loading footer from partition '", - out_partition_name, - "'\n", - NULL); + avb_error( + "Error loading footer from partition '", out_partition_name, "'\n"); goto out; } if (avb_memcmp(footer.magic, AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) { - avb_errorv("Data from '", - out_partition_name, - "' does not look like a vbmeta footer.\n", - NULL); + avb_error("Data from '", + out_partition_name, + "' does not look like a vbmeta footer.\n"); goto out; } @@ -111,8 +108,7 @@ static bool load_top_level_vbmeta_header( } if (io_res != AVB_IO_RESULT_OK) { - avb_errorv( - "Error loading from partition '", out_partition_name, "'\n", NULL); + avb_error("Error loading from partition '", out_partition_name, "'\n"); goto out; } @@ -141,10 +137,9 @@ bool avb_user_verification_get(AvbOps* ops, } if (avb_memcmp(vbmeta_image, AVB_MAGIC, AVB_MAGIC_LEN) != 0) { - avb_errorv("Data from '", - partition_name, - "' does not look like a vbmeta header.\n", - NULL); + avb_error("Data from '", + partition_name, + "' does not look like a vbmeta header.\n"); goto out; } @@ -180,10 +175,9 @@ bool avb_user_verification_set(AvbOps* ops, } if (avb_memcmp(vbmeta_image, AVB_MAGIC, AVB_MAGIC_LEN) != 0) { - avb_errorv("Data from '", - partition_name, - "' does not look like a vbmeta header.\n", - NULL); + avb_error("Data from '", + partition_name, + "' does not look like a vbmeta header.\n"); goto out; } @@ -203,7 +197,7 @@ bool avb_user_verification_set(AvbOps* ops, AVB_VBMETA_IMAGE_HEADER_SIZE, vbmeta_image); if (io_res != AVB_IO_RESULT_OK) { - avb_errorv("Error writing to partition '", partition_name, "'\n", NULL); + avb_error("Error writing to partition '", partition_name, "'\n"); goto out; } diff --git a/libavb_user/avb_user_verity.c b/libavb_user/avb_user_verity.c index ecf0043..dd64b3c 100644 --- a/libavb_user/avb_user_verity.c +++ b/libavb_user/avb_user_verity.c @@ -86,18 +86,15 @@ static bool load_top_level_vbmeta_header( &footer, &num_read); if (io_res != AVB_IO_RESULT_OK) { - avb_errorv("Error loading footer from partition '", - out_partition_name, - "'\n", - NULL); + avb_error( + "Error loading footer from partition '", out_partition_name, "'\n"); goto out; } if (avb_memcmp(footer.magic, AVB_FOOTER_MAGIC, AVB_FOOTER_MAGIC_LEN) != 0) { - avb_errorv("Data from '", - out_partition_name, - "' does not look like a vbmeta footer.\n", - NULL); + avb_error("Data from '", + out_partition_name, + "' does not look like a vbmeta footer.\n"); goto out; } @@ -111,8 +108,7 @@ static bool load_top_level_vbmeta_header( } if (io_res != AVB_IO_RESULT_OK) { - avb_errorv( - "Error loading from partition '", out_partition_name, "'\n", NULL); + avb_error("Error loading from partition '", out_partition_name, "'\n"); goto out; } @@ -141,10 +137,9 @@ bool avb_user_verity_get(AvbOps* ops, } if (avb_memcmp(vbmeta_image, AVB_MAGIC, AVB_MAGIC_LEN) != 0) { - avb_errorv("Data from '", - partition_name, - "' does not look like a vbmeta header.\n", - NULL); + avb_error("Data from '", + partition_name, + "' does not look like a vbmeta header.\n"); goto out; } @@ -179,10 +174,9 @@ bool avb_user_verity_set(AvbOps* ops, } if (avb_memcmp(vbmeta_image, AVB_MAGIC, AVB_MAGIC_LEN) != 0) { - avb_errorv("Data from '", - partition_name, - "' does not look like a vbmeta header.\n", - NULL); + avb_error("Data from '", + partition_name, + "' does not look like a vbmeta header.\n"); goto out; } @@ -202,7 +196,7 @@ bool avb_user_verity_set(AvbOps* ops, AVB_VBMETA_IMAGE_HEADER_SIZE, vbmeta_image); if (io_res != AVB_IO_RESULT_OK) { - avb_errorv("Error writing to partition '", partition_name, "'\n", NULL); + avb_error("Error writing to partition '", partition_name, "'\n"); goto out; } diff --git a/rust/Android.bp b/rust/Android.bp new file mode 100644 index 0000000..9188819 --- /dev/null +++ b/rust/Android.bp @@ -0,0 +1,520 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Rust bindgen wrappers to allow calling into libavb from Rust. +// +// The auto-generated wrappers are Rust unsafe and somewhat difficult to work +// with so are not exposed outside of this directory; instead we will provide +// a safe higher-level Rust API. +rust_defaults { + name: "libavb_bindgen.common.defaults", + wrapper_src: "bindgen/avb.h", + crate_name: "avb_bindgen", + edition: "2021", + visibility: [ + ":__subpackages__", + // TODO(b/290110273): add the Rust public API layer here and adjust + // Virtualization packages to depend on it instead of the raw bindgen. + "//packages/modules/Virtualization:__subpackages__", + ], + source_stem: "bindings", + bindgen_flags: [ + "--constified-enum-module=AvbDescriptorTag", + "--bitfield-enum=Avb.*Flags", + "--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::{AsBytes, FromBytes, FromZeroes};", + "--ctypes-prefix=core::ffi", + ], + cflags: ["-DBORINGSSL_NO_CXX"], +} + +// Full bindgen defaults for std targets. +rust_defaults { + name: "libavb_bindgen.std.defaults", + defaults: ["libavb_bindgen.common.defaults"], + host_supported: true, + static_libs: ["libavb_cert"], + shared_libs: ["libcrypto"], + rustlibs: ["libzerocopy"], + apex_available: ["com.android.virt"], +} + +// Full bindgen default for nostd targets. +rust_defaults { + name: "libavb_bindgen.nostd.defaults", + defaults: ["libavb_bindgen.common.defaults"], + static_libs: [ + "libavb_cert_baremetal", + "libcrypto_baremetal", + ], + rustlibs: ["libzerocopy_nostd_noalloc"], +} + +// Internal source-only bindgen with std. +// +// This target should only be used as `srcs`, not `rustlibs` or `rlibs`. This +// is because the `rust_bindgen` rule intentionally only generates rlibs +// (b/166332519), and also forces its dependencies to use rlibs. However, this +// can create mismatched library types if the depenency is also used elsewhere +// in a build rule as a dylib. In particular for us, libzerocopy and its own +// dependency libbyteorder trigger this problem like so: +// +// build target (prefer dylib) +// / \ +// libavb_rs (dylib) \ +// / \ +// libavb_bindgen (rlib) ... arbitrary dependency chain (dylib) ... +// / \ +// libzerocopy (rlib) \ +// / \ +// libbyteorder (rlib) libbyteorder (dylib) +// +// By using it as a `srcs` instead, we can wrap it in a `rust_library` which +// allows selecting either library type and fixes the conflict: +// +// build target (prefer dylib) +// / \ +// libavb_rs (dylib) \ +// / \ +// libavb_bindgen (dylib) ... arbitrary dependency chain (dylib) ... +// / / +// libzerocopy (dylib) / +// \ / +// libbyteorder (dylib) +// +rust_bindgen { + name: "libavb_bindgen_for_srcs_only", + defaults: ["libavb_bindgen.std.defaults"], +} + +// Bindgen with std. +// +// See above for why we need a `rust_library` wrapper here. +rust_library { + name: "libavb_bindgen", + defaults: ["libavb_bindgen.std.defaults"], + srcs: [":libavb_bindgen_for_srcs_only"], +} + +// Bindgen nostd. +// +// Nostd targets always use rlibs, so we don't need a `rust_library` wrapper in +// this case; the rlib-only bindgen target is sufficient. +rust_bindgen { + name: "libavb_bindgen_nostd", + defaults: ["libavb_bindgen.nostd.defaults"], +} + +// Bindgen auto-generated tests. +rust_test { + name: "libavb_bindgen_test", + srcs: [":libavb_bindgen_for_srcs_only"], + crate_name: "avb_bindgen_test", + edition: "2021", + test_suites: ["general-tests"], + auto_gen_config: true, + clippy_lints: "none", + lints: "none", + rustlibs: ["libzerocopy"], +} + +// Rust library wrapping libavb C implementation. + +// Common defaults for all variations. +rust_defaults { + name: "libavb_rs_common.defaults", + crate_name: "avb", + srcs: ["src/lib.rs"], + clippy_lints: "android", + lints: "android", +} + +// No std, no features. +rust_defaults { + name: "libavb_rs_nostd.defaults", + defaults: ["libavb_rs_common.defaults"], + // Only rlib can build without the required nostd hooks (eh_personality, + // panic_handler, etc) to defer them for the final binary to implement. + prefer_rlib: true, + no_stdlibs: true, + rustlibs: [ + "libavb_bindgen_nostd", + "libzerocopy_nostd_noalloc", + ], + whole_static_libs: [ + "libavb_cert_baremetal", + ], + stdlibs: [ + "libcore.rust_sysroot", + ], +} + +// Std, no features. +rust_defaults { + name: "libavb_rs.defaults", + defaults: ["libavb_rs_common.defaults"], + host_supported: true, + rustlibs: [ + "libavb_bindgen", + "libzerocopy", + ], + whole_static_libs: [ + "libavb_cert", + ], +} + +// Adds UUID feature for nostd. +rust_defaults { + name: "libavb_rs_nostd.uuid.defaults", + features: [ + "uuid", + ], + rustlibs: [ + "libuuid_nostd", + ], +} + +// Adds UUID feature for std. +rust_defaults { + name: "libavb_rs.uuid.defaults", + features: [ + "uuid", + ], + rustlibs: [ + "libuuid", + ], +} + +// lib: no std, no features. +rust_library_rlib { + name: "libavb_rs_nostd", + defaults: ["libavb_rs_nostd.defaults"], +} + +// lib: no std, UUID feature. +rust_library_rlib { + name: "libavb_rs_nostd_uuid", + defaults: [ + "libavb_rs_nostd.defaults", + "libavb_rs_nostd.uuid.defaults", + ], +} + +// lib: std, no features. +rust_library { + name: "libavb_rs", + defaults: ["libavb_rs.defaults"], +} + +// lib: std, UUID feature. +rust_library { + name: "libavb_rs_uuid", + defaults: [ + "libavb_rs.defaults", + "libavb_rs.uuid.defaults", + ], +} + +// TestOps lib: std +rust_library { + crate_name: "avb_test", + name: "libavb_test_rs_testops", + srcs: ["tests/test_ops.rs"], + clippy_lints: "android", + lints: "android", + host_supported: true, + rustlibs: [ + "libavb_rs", + ], + whole_static_libs: [ + "libavb_cert", + ], +} + +// "libavb_rs.defaults" plus additional unit test defaults. +rust_defaults { + name: "libavb_rs_unittest.defaults", + defaults: ["libavb_rs.defaults"], + data: [":libavb_rs_example_descriptors"], + test_suites: ["general-tests"], +} + +// Unit tests: std, no features. +rust_test { + name: "libavb_rs_unittest", + defaults: ["libavb_rs_unittest.defaults"], +} + +// Unit tests: std, UUID feature. +rust_test { + name: "libavb_rs_uuid_unittest", + defaults: [ + "libavb_rs_unittest.defaults", + "libavb_rs.uuid.defaults", + ], +} + +// Example descriptors in binary format. +filegroup { + name: "libavb_rs_example_descriptors", + srcs: [ + "testdata/chain_partition_descriptor.bin", + "testdata/hash_descriptor.bin", + "testdata/hashtree_descriptor.bin", + "testdata/kernel_commandline_descriptor.bin", + "testdata/property_descriptor.bin", + ], +} + +// Integration test defaults. +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", + ":avbrs_test_image_with_vbmeta_footer", + ":avbrs_test_image_with_vbmeta_footer_for_boot", + ":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", + "libzerocopy", + ], + test_suites: ["general-tests"], + clippy_lints: "android", + lints: "android", +} + +// Integration test: no features. +rust_test { + name: "libavb_rs_test", + defaults: ["libavb_rs_test.defaults"], + rustlibs: ["libavb_rs"], +} + +// Integration test: UUID feature. +rust_test { + name: "libavb_rs_uuid_test", + defaults: [ + "libavb_rs.uuid.defaults", + "libavb_rs_test.defaults", + ], + rustlibs: ["libavb_rs_uuid"], +} + +// Test images for verification. + +// Unsigned 16KiB test image. +genrule { + name: "avbrs_test_image", + tools: ["avbtool"], + out: ["test_image.img"], + cmd: "$(location avbtool) generate_test_image --image_size 16384 --output $(out)", +} + +// Unsigned vbmeta blob containing the test image descriptor for partition name "test_part". +avb_gen_vbmeta_image { + name: "avbrs_test_image_descriptor", + src: ":avbrs_test_image", + partition_name: "test_part", + salt: "1000", +} + +// Unsigned vbmeta blob containing the test image descriptor for partition name "test_part_2". +avb_gen_vbmeta_image { + name: "avbrs_test_image_descriptor_2", + src: ":avbrs_test_image", + partition_name: "test_part_2", + salt: "1001", +} + +// Unsigned vbmeta blob containing a persistent digest descriptor for partition name +// "test_part_persistent_digest". +// +// Currently this is the only in-tree usage of persistent digests, but if anyone else needs it +// later on it may be worth folding support for this into the `avb_gen_vbmeta_image` rule. +genrule { + name: "avbrs_test_image_descriptor_persistent_digest", + tools: ["avbtool"], + srcs: [":avbrs_test_image"], + out: ["avbrs_test_image_descriptor_persistent_digest.img"], + cmd: "$(location avbtool) add_hash_footer --image $(location :avbrs_test_image) --partition_name test_part_persistent_digest --dynamic_partition_size --do_not_append_vbmeta_image --use_persistent_digest --output_vbmeta_image $(out)", +} + +// Unsigned vbmeta blob containing a hastree descriptor for partition name +// "test_part_hashtree". +genrule { + name: "avbrs_test_image_descriptor_hashtree", + tools: ["avbtool"], + srcs: [":avbrs_test_image"], + out: ["avbrs_test_image_descriptor_hashtree.img"], + // Generating FEC values requires the `fec` tool to be on $PATH, which does + // not seems to be possible here. For now pass `--do_not_generate_fec`. + cmd: "$(location avbtool) add_hashtree_footer --image $(location :avbrs_test_image) --partition_name test_part_hashtree --partition_size 0 --salt B000 --do_not_append_vbmeta_image --output_vbmeta_image $(out) --do_not_generate_fec", +} + +// Standalone vbmeta image signing the test image descriptor. +genrule { + name: "avbrs_test_vbmeta", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta.img"], + 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", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor", + ":avbrs_test_image_descriptor_2", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta_2_parts.img"], + cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --include_descriptors_from_image $(location :avbrs_test_image_descriptor_2) --output $(out)", +} + +// Standalone vbmeta image signing the test image persistent digest descriptor. +genrule { + name: "avbrs_test_vbmeta_persistent_digest", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor_persistent_digest", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta_persistent_digest.img"], + cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor_persistent_digest) --output $(out)", +} + +// Standalone vbmeta image with property descriptor "test_prop_key" = "test_prop_value". +genrule { + name: "avbrs_test_vbmeta_with_property", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta_with_property.img"], + cmd: "$(location avbtool) make_vbmeta_image --prop test_prop_key:test_prop_value --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)", +} + +// Standalone vbmeta image with the test image hashtree descriptor. +genrule { + name: "avbrs_test_vbmeta_with_hashtree", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor_hashtree", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta_with_hashtree.img"], + cmd: "$(location avbtool) make_vbmeta_image --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor_hashtree) --output $(out)", +} + +// Standalone vbmeta image with kernel commandline "test_cmdline_key=test_cmdline_value". +genrule { + name: "avbrs_test_vbmeta_with_commandline", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor", + ":avb_testkey_rsa4096", + ], + out: ["test_vbmeta_with_commandline.img"], + cmd: "$(location avbtool) make_vbmeta_image --kernel_cmdline test_cmdline_key=test_cmdline_value --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)", +} + +// Standalone vbmeta image with chain descriptor to "test_part_2" with rollback +// index 4, signed by avb_testkey_rsa8192. +genrule { + name: "avbrs_test_vbmeta_with_chained_partition", + tools: ["avbtool"], + srcs: [ + ":avbrs_test_image_descriptor", + ":avb_testkey_rsa4096", + ":avb_testkey_rsa8192_pub_bin", + ], + out: ["test_vbmeta_with_chained_partition.img"], + cmd: "$(location avbtool) make_vbmeta_image --chain_partition test_part_2:4:$(location :avb_testkey_rsa8192_pub_bin) --key $(location :avb_testkey_rsa4096) --algorithm SHA512_RSA4096 --include_descriptors_from_image $(location :avbrs_test_image_descriptor) --output $(out)", +} + +// Combined test image + signed vbmeta footer for "test_part". +avb_add_hash_footer { + name: "avbrs_test_image_with_vbmeta_footer", + src: ":avbrs_test_image", + partition_name: "test_part", + private_key: ":avb_testkey_rsa4096", + salt: "A000", +} + +// Combined test image + signed vbmeta footer for "boot". +avb_add_hash_footer { + name: "avbrs_test_image_with_vbmeta_footer_for_boot", + src: ":avbrs_test_image", + partition_name: "boot", + private_key: ":avb_testkey_rsa4096", + salt: "A001", +} + +// Combined test image + signed vbmeta footer for "test_part_2" signed by +// avb_testkey_rsa8192 with rollback index = 7. +avb_add_hash_footer { + name: "avbrs_test_image_with_vbmeta_footer_for_test_part_2", + src: ":avbrs_test_image", + partition_name: "test_part_2", + private_key: ":avb_testkey_rsa8192", + algorithm: "SHA256_RSA8192", + salt: "A002", + rollback_index: 7, +} diff --git a/rust/OWNERS b/rust/OWNERS new file mode 100644 index 0000000..e8e7f5e --- /dev/null +++ b/rust/OWNERS @@ -0,0 +1,2 @@ +aliceywang@google.com +dpursell@google.com diff --git a/rust/TEST_MAPPING b/rust/TEST_MAPPING new file mode 100644 index 0000000..66173f9 --- /dev/null +++ b/rust/TEST_MAPPING @@ -0,0 +1,19 @@ +{ + "presubmit": [ + { + "name": "libavb_bindgen_test" + }, + { + "name": "libavb_rs_test" + }, + { + "name": "libavb_rs_unittest" + }, + { + "name": "libavb_rs_uuid_test" + }, + { + "name": "libavb_rs_uuid_unittest" + } + ] +}
\ No newline at end of file diff --git a/rust/bindgen/avb.h b/rust/bindgen/avb.h new file mode 100644 index 0000000..3d841c1 --- /dev/null +++ b/rust/bindgen/avb.h @@ -0,0 +1,20 @@ +/* + * Copyright (C) 2021 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. + */ + +#pragma once + +#include <libavb/libavb.h> +#include <libavb_cert/libavb_cert.h> diff --git a/rust/src/cert.rs b/rust/src/cert.rs new file mode 100644 index 0000000..aa54859 --- /dev/null +++ b/rust/src/cert.rs @@ -0,0 +1,399 @@ +// 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_cert support. +//! +//! libavb_cert is an optional extension on top of the standard libavb API. It provides two +//! additional features: +//! +//! 1. Key management +//! 2. Authenticated unlock +//! +//! # Key management +//! The standard avb `Ops` must provide callbacks to manually validate vbmeta signing keys. This can +//! become complicated when using best-practices such as key heirarchies and rotations, which often +//! results in implementations omitting these features and just using a single fixed key. +//! +//! libavb_cert enables these features more easily by internally managing a set of related keys: +//! +//! * Product root key (PRK): un-rotateable root key +//! * Product intermediate key (PIK): rotateable key signed by the PRK +//! * Product signing key (PSK): rotateable key signed by the PIK, used as the vbmeta key +//! +//! PIK and PSK rotations are supported by storing their versions as rollback indices, so that +//! once the keys have been rotated the rollback value updates and the older keys will no longer +//! be accepted. +//! +//! The device validates keys using a fixed blob of data called "permanent attributes", which can +//! authenticate via the PRK and never needs to change even when PIK/PSK are rotated. +//! +//! To use this functionality, implement the `CertOps` trait and forward +//! `validate_vbmeta_public_key()` and/or `validate_public_key_for_partition()` to the provided +//! `cert_validate_vbmeta_public_key()` implementation. +//! +//! # Authenticated unlock +//! Typically devices support fastboot commands such as `fastboot flashing unlock` to unlock the +//! bootloader. Authenticated unlock is an optional feature that additionally adds an authentication +//! requirement in order to unlock the bootloader. +//! +//! Authenticated unlock introduces one additional key, the product unlock key (PUK), which is +//! signed by the PIK. The PUK is in the same key heirarchy but a distinct key, so that access to +//! the PUK does not give the ability to sign images. When authenticated unlock is requested, +//! libavb_cert produces a randomized "challenge token" which the user must then properly sign with +//! the PUK in order to unlock. +//! +//! It's up to individual device policy how to use authenticated unlock. For example a device may +//! want to support standard un-authenticated unlock for most operations, but then additionally +//! use authenticated unlock to enable higher-privileged operations. +//! +//! An example unlock flow using fastboot might look like this: +//! +//! ```ignore +//! # 1. Generate an unlock challenge (the exact fastboot command is device-specific). +//! $ fastboot oem get-auth-unlock-challenge +//! +//! # Internally, the device calls `cert_generate_unlock_challenge()` to generate the token. +//! +//! # 2. Download the challenge token from the device. +//! $ fastboot get_staged /tmp/challenge.bin +//! +//! # 3. Sign the challenge with the PUK. +//! $ avbtool make_cert_unlock_credential \ +//! --challenge /tmp/challenge.bin \ +//! --output /tmp/signed.bin \ +//! ... # see --help for full args +//! +//! # 4. Upload the signed credential back to the device. +//! $ fastboot stage /tmp/signed.bin +//! +//! # 5. Unlock the device (the exact fastboot command is device-specific). +//! $ fastboot oem auth-unlock +//! +//! # Internally, the device calls `cert_validate_unlock_credential()` to verify the credential. +//! ``` + +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; + +/// Authenticated unlock challenge. +pub use avb_bindgen::AvbCertUnlockChallenge as CertUnlockChallenge; + +/// Signed authenticated unlock credential. +pub use avb_bindgen::AvbCertUnlockCredential as CertUnlockCredential; + +/// Size in bytes of a SHA256 digest. +pub const SHA256_DIGEST_SIZE: usize = avb_bindgen::AVB_SHA256_DIGEST_SIZE as usize; + +/// Product intermediate key (PIK) rollback index location. +/// +/// If using libavb_cert, make sure no vbmetas use this location, it must be reserved for the PIK. +pub const CERT_PIK_VERSION_LOCATION: usize = avb_bindgen::AVB_CERT_PIK_VERSION_LOCATION as usize; + +/// Product signing key (PSK) rollback index location. +/// +/// If using libavb_cert, make sure no vbmetas use this location, it must be reserved for the PSK. +pub const CERT_PSK_VERSION_LOCATION: usize = avb_bindgen::AVB_CERT_PSK_VERSION_LOCATION as usize; + +/// libavb_cert extension callbacks. +pub trait CertOps { + /// Reads the device's permanent attributes. + /// + /// The full permanent attributes are not required to be securely stored; corruption of this + /// data will result in failing to verify the images (denial-of-service), but will not change + /// the signing keys or allow improperly-signed images to verify. + /// + /// # Arguments + /// * `attributes`: permanent attributes to update; passed as an output parameter rather than a + /// return value due to the size (>1KiB). + /// + /// # Returns + /// Unit on success, error on failure. + fn read_permanent_attributes( + &mut self, + attributes: &mut CertPermanentAttributes, + ) -> IoResult<()>; + + /// Reads the SHA256 hash of the device's permanent attributes. + /// + /// This hash must be sourced from secure storage whenever the device is locked; corruption + /// of this data could result in changing the signing keys and allowing improperly-signed images + /// to pass verification. + /// + /// This may be calculated at runtime from `read_permanent_attributes()` only if the entire + /// permanent attributes are sourced from secure storage, but secure storage space is often + /// limited so it can be useful to only store the hash securely. + /// + /// # Returns + /// The 32-byte SHA256 digest on success, error on failure. + fn read_permanent_attributes_hash(&mut self) -> IoResult<[u8; SHA256_DIGEST_SIZE]>; + + /// Provides the key version for the rotateable keys. + /// + /// libavb_cert stores signing key versions as rollback indices; when this function is called it + /// indicates that the key at the given index location is using the given version. + /// + /// The exact steps to take when receiving this callback depend on device policy, but generally + /// these values should only be cached in this callback, and written to the rollback storage + /// only after the images are known to be successful. + /// + /// For example, a device using A/B boot slots should not update the key version rollbacks + /// until it knows for sure the new image works, otherwise an OTA could break the A/B fallback + /// behavior by updating the key version too soon and preventing falling back to the previous + /// slot. + /// + /// # Arguments + /// * `rollback_index_location`: rollback location to store this key version + /// * `key_version`: value to store in the rollback location + /// + /// # Returns + /// `None`; since the rollback should be cached rather than written immediately, this function + /// cannot fail. + fn set_key_version(&mut self, rollback_index_location: usize, key_version: u64); + + /// Generates random bytes. + /// + /// This is only used for authenticated unlock. If authenticated unlock is not needed, this can + /// just return `IoError::NotImplemented`. + /// + /// # Arguments + /// * `bytes`: buffer to completely fill with random bytes. + /// + /// # Returns + /// Unit on success, error on failure. + fn get_random(&mut self, bytes: &mut [u8]) -> IoResult<()>; +} + +/// Certificate-based vbmeta key validation. +/// +/// This can be called from `validate_vbmeta_public_key()` or `validate_public_key_for_partition()` +/// to provide the correct behavior using the libavb_cert keys, such as: +/// +/// ``` +/// impl avb::Ops for MyOps { +/// fn validate_vbmeta_public_key( +/// &mut self, +/// public_key: &[u8], +/// public_key_metadata: Option<&[u8]>, +/// ) -> IoResult<bool> { +/// cert_validate_vbmeta_public_key(self, public_key, public_key_metadata) +/// } +/// } +/// ``` +/// +/// We don't automatically call this from the validation functions because it's up to the device +/// when to use certificate authentication e.g. a device may want to use libavb_cert only for +/// specific partitions. +/// +/// # Arguments +/// * `ops`: the `Ops` callback implementations, which must provide a `cert_ops()` implementation. +/// * `public_key`: the public key. +/// * `public_key_metadata`: public key metadata. +/// +/// # Returns +/// * `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]>, +) -> IoResult<bool> { + // 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. +/// +/// Used to create a challenge token to be signed with the PUK. +/// +/// The user can sign the resulting token via `avbtool make_cert_unlock_credential`. +/// +/// # Arguments +/// * `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> { + // `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. +/// +/// Used to check that an unlock credential was properly signed with the PUK according to the +/// device's permanent attributes. +/// +/// # Arguments +/// * `ops`: the `Ops` callback implementations, which must provide a `cert_ops()` implementation. +/// * `credential`: the signed unlock credential to verify. +/// +/// # 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, +) -> IoResult<bool> { + // 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/descriptor/chain.rs b/rust/src/descriptor/chain.rs new file mode 100644 index 0000000..c579992 --- /dev/null +++ b/rust/src/descriptor/chain.rs @@ -0,0 +1,117 @@ +// 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. + +//! Chain partition descriptors. + +use super::{ + util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc}, + DescriptorResult, +}; +use avb_bindgen::{ + avb_chain_partition_descriptor_validate_and_byteswap, AvbChainPartitionDescriptor, +}; +use core::str::from_utf8; + +/// `AvbChainPartitionDescriptorFlags`; see libavb docs for details. +pub use avb_bindgen::AvbChainPartitionDescriptorFlags as ChainPartitionDescriptorFlags; + +/// Wraps a chain partition descriptor stored in a vbmeta image. +#[derive(Debug, PartialEq, Eq)] +pub struct ChainPartitionDescriptor<'a> { + /// Chained partition rollback index location. + pub rollback_index_location: u32, + + /// Chained partition name. + /// + /// Most partition names in this library are passed as `&CStr`, but inside + /// descriptors the partition names are not nul-terminated making them + /// ineligible for use directly as `&CStr`. If `&CStr` is required, one + /// option is to allocate a nul-terminated copy of this string via + /// `CString::new()` which can then be converted to `&CStr`. + pub partition_name: &'a str, + + /// Chained partition public key. + pub public_key: &'a [u8], + + /// Flags. + pub flags: ChainPartitionDescriptorFlags, +} + +// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type. +unsafe impl ValidateAndByteswap for AvbChainPartitionDescriptor { + const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> = + avb_chain_partition_descriptor_validate_and_byteswap; +} + +impl<'a> ChainPartitionDescriptor<'a> { + /// Extract a `ChainPartitionDescriptor` from the given descriptor contents. + /// + /// # Arguments + /// * `contents`: descriptor contents, including the header, in raw big-endian format. + /// + /// # Returns + /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid + /// `AvbChainPartitionDescriptor`. + pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> { + // Descriptor contains: header + partition name + public key. + let descriptor = parse_descriptor::<AvbChainPartitionDescriptor>(contents)?; + let (partition_name, remainder) = + split_slice(descriptor.body, descriptor.header.partition_name_len)?; + let (public_key, _) = split_slice(remainder, descriptor.header.public_key_len)?; + + Ok(Self { + flags: ChainPartitionDescriptorFlags(descriptor.header.flags), + partition_name: from_utf8(partition_name)?, + rollback_index_location: descriptor.header.rollback_index_location, + public_key, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::DescriptorError; + use std::{fs, mem::size_of}; + + /// A valid descriptor that we've pre-generated as test data. + fn test_contents() -> Vec<u8> { + fs::read("testdata/chain_partition_descriptor.bin").unwrap() + } + + #[test] + fn new_chain_partition_descriptor_success() { + assert!(ChainPartitionDescriptor::new(&test_contents()).is_ok()); + } + + #[test] + fn new_chain_partition_descriptor_too_short_header_fails() { + let bad_header_size = size_of::<AvbChainPartitionDescriptor>() - 1; + assert_eq!( + ChainPartitionDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(), + DescriptorError::InvalidHeader + ); + } + + #[test] + fn new_chain_partition_descriptor_too_short_contents_fails() { + // The last byte is padding, so we need to drop 2 bytes to trigger an error. + let bad_contents_size = test_contents().len() - 2; + assert_eq!( + ChainPartitionDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(), + DescriptorError::InvalidSize + ); + } +} diff --git a/rust/src/descriptor/commandline.rs b/rust/src/descriptor/commandline.rs new file mode 100644 index 0000000..a9afbb4 --- /dev/null +++ b/rust/src/descriptor/commandline.rs @@ -0,0 +1,102 @@ +// 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. + +//! Kernel commandline descriptors. + +use super::{ + util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc}, + DescriptorResult, +}; +use avb_bindgen::{ + avb_kernel_cmdline_descriptor_validate_and_byteswap, AvbKernelCmdlineDescriptor, +}; +use core::str::from_utf8; + +/// `AvbKernelCmdlineFlags`; see libavb docs for details. +pub use avb_bindgen::AvbKernelCmdlineFlags as KernelCommandlineDescriptorFlags; + +/// Wraps an `AvbKernelCmdlineDescriptor` stored in a vbmeta image. +#[derive(Debug, PartialEq, Eq)] +pub struct KernelCommandlineDescriptor<'a> { + /// Flags. + pub flags: KernelCommandlineDescriptorFlags, + + /// Kernel commandline. + pub commandline: &'a str, +} + +// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type. +unsafe impl ValidateAndByteswap for AvbKernelCmdlineDescriptor { + const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> = + avb_kernel_cmdline_descriptor_validate_and_byteswap; +} + +impl<'a> KernelCommandlineDescriptor<'a> { + /// Extracts a `KernelCommandlineDescriptor` from the given descriptor contents. + /// + /// # Arguments + /// * `contents`: descriptor contents, including the header, in raw big-endian format. + /// + /// # Returns + /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid + /// `AvbKernelCmdlineDescriptor`. + pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> { + // Descriptor contains: header + commandline. + let descriptor = parse_descriptor::<AvbKernelCmdlineDescriptor>(contents)?; + let (commandline, _) = + split_slice(descriptor.body, descriptor.header.kernel_cmdline_length)?; + + Ok(Self { + flags: KernelCommandlineDescriptorFlags(descriptor.header.flags), + commandline: from_utf8(commandline)?, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::DescriptorError; + use std::{fs, mem::size_of}; + + /// A valid descriptor that we've pre-generated as test data. + fn test_contents() -> Vec<u8> { + fs::read("testdata/kernel_commandline_descriptor.bin").unwrap() + } + + #[test] + fn new_commandline_descriptor_success() { + assert!(KernelCommandlineDescriptor::new(&test_contents()).is_ok()); + } + + #[test] + fn new_commandline_descriptor_too_short_header_fails() { + let bad_header_size = size_of::<KernelCommandlineDescriptor>() - 1; + assert_eq!( + KernelCommandlineDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(), + DescriptorError::InvalidHeader + ); + } + + #[test] + fn new_commandline_descriptor_too_short_contents_fails() { + // The last 5 bytes are padding, so we need to drop 6 bytes to trigger an error. + let bad_contents_size = test_contents().len() - 6; + assert_eq!( + KernelCommandlineDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(), + DescriptorError::InvalidSize + ); + } +} diff --git a/rust/src/descriptor/hash.rs b/rust/src/descriptor/hash.rs new file mode 100644 index 0000000..be7fb5b --- /dev/null +++ b/rust/src/descriptor/hash.rs @@ -0,0 +1,131 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Hash descriptors. + +use super::{ + util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc}, + DescriptorResult, +}; +use avb_bindgen::{avb_hash_descriptor_validate_and_byteswap, AvbHashDescriptor}; +use core::{ffi::CStr, str::from_utf8}; + +/// `AvbHashDescriptorFlags`; see libavb docs for details. +pub use avb_bindgen::AvbHashDescriptorFlags as HashDescriptorFlags; + +/// Wraps a Hash descriptor stored in a vbmeta image. +#[derive(Debug, PartialEq, Eq)] +pub struct HashDescriptor<'a> { + /// The size of the hashed image. + pub image_size: u64, + + /// Hash algorithm name. + pub hash_algorithm: &'a str, + + /// Flags. + pub flags: HashDescriptorFlags, + + /// Partition name. + /// + /// Most partition names in this library are passed as `&CStr`, but inside + /// descriptors the partition names are not nul-terminated making them + /// ineligible for use directly as `&CStr`. If `&CStr` is required, one + /// option is to allocate a nul-terminated copy of this string via + /// `CString::new()` which can then be converted to `&CStr`. + pub partition_name: &'a str, + + /// Salt used to hash the image. + pub salt: &'a [u8], + + /// Image hash digest. + pub digest: &'a [u8], +} + +// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type. +unsafe impl ValidateAndByteswap for AvbHashDescriptor { + const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> = + avb_hash_descriptor_validate_and_byteswap; +} + +impl<'a> HashDescriptor<'a> { + /// Extract a `HashDescriptor` from the given descriptor contents. + /// + /// # Arguments + /// * `contents`: descriptor contents, including the header, in raw big-endian format. + /// + /// # Returns + /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid + /// `AvbHashDescriptor`. + pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> { + // Descriptor contains: header + name + salt + digest. + let descriptor = parse_descriptor::<AvbHashDescriptor>(contents)?; + let (partition_name, remainder) = + split_slice(descriptor.body, descriptor.header.partition_name_len)?; + let (salt, remainder) = split_slice(remainder, descriptor.header.salt_len)?; + let (digest, _) = split_slice(remainder, descriptor.header.digest_len)?; + + // Extract the hash algorithm from the original raw header since the temporary + // byte-swapped header doesn't live past this function. + // The hash algorithm is a nul-terminated UTF-8 string which is identical in the raw + // and byteswapped headers. + let hash_algorithm = + CStr::from_bytes_until_nul(&descriptor.raw_header.hash_algorithm)?.to_str()?; + + Ok(Self { + image_size: descriptor.header.image_size, + hash_algorithm, + flags: HashDescriptorFlags(descriptor.header.flags), + partition_name: from_utf8(partition_name)?, + salt, + digest, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::DescriptorError; + use std::{fs, mem::size_of}; + + /// A valid descriptor that we've pre-generated as test data. + fn test_contents() -> Vec<u8> { + fs::read("testdata/hash_descriptor.bin").unwrap() + } + + #[test] + fn new_hash_descriptor_success() { + assert!(HashDescriptor::new(&test_contents()).is_ok()); + } + + #[test] + fn new_hash_descriptor_too_short_header_fails() { + let bad_header_size = size_of::<AvbHashDescriptor>() - 1; + assert_eq!( + HashDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(), + DescriptorError::InvalidHeader + ); + } + + #[test] + fn new_hash_descriptor_too_short_contents_fails() { + // The last byte is padding, so we need to drop 2 bytes to trigger an error. + let bad_contents_size = test_contents().len() - 2; + assert_eq!( + HashDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(), + DescriptorError::InvalidSize + ); + } +} diff --git a/rust/src/descriptor/hashtree.rs b/rust/src/descriptor/hashtree.rs new file mode 100644 index 0000000..cc2ee8c --- /dev/null +++ b/rust/src/descriptor/hashtree.rs @@ -0,0 +1,157 @@ +// 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. + +//! Hashtree descriptors. + +use super::{ + util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc}, + DescriptorResult, +}; +use avb_bindgen::{avb_hashtree_descriptor_validate_and_byteswap, AvbHashtreeDescriptor}; +use core::{ffi::CStr, str::from_utf8}; + +/// `AvbHashtreeDescriptorFlags`; see libavb docs for details. +pub use avb_bindgen::AvbHashtreeDescriptorFlags as HashtreeDescriptorFlags; + +/// Wraps a Hashtree descriptor stored in a vbmeta image. +#[derive(Debug, PartialEq, Eq)] +pub struct HashtreeDescriptor<'a> { + /// DM-Verity version. + pub dm_verity_version: u32, + + /// Hashed image size. + pub image_size: u64, + + /// Offset to the root block of the hash tree. + pub tree_offset: u64, + + /// Hash tree size. + pub tree_size: u64, + + /// Data block size in bytes. + pub data_block_size: u32, + + /// Hash block size in bytes. + pub hash_block_size: u32, + + /// Number of forward error correction roots. + pub fec_num_roots: u32, + + /// Offset to the forward error correction data. + pub fec_offset: u64, + + /// Forward error correction data size. + pub fec_size: u64, + + /// Hash algorithm name. + pub hash_algorithm: &'a str, + + /// Flags. + pub flags: HashtreeDescriptorFlags, + + /// Partition name. + pub partition_name: &'a str, + + /// Salt used to hash the image. + pub salt: &'a [u8], + + /// Image root hash digest. + pub root_digest: &'a [u8], +} + +// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type. +unsafe impl ValidateAndByteswap for AvbHashtreeDescriptor { + const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> = + avb_hashtree_descriptor_validate_and_byteswap; +} + +impl<'a> HashtreeDescriptor<'a> { + /// Extract a `HashtreeDescriptor` from the given descriptor contents. + /// + /// # Arguments + /// * `contents`: descriptor contents, including the header, in raw big-endian format. + /// + /// # Returns + /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid + /// `AvbHashtreeDescriptor`. + pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> { + // Descriptor contains: header + name + salt + digest. + let descriptor = parse_descriptor::<AvbHashtreeDescriptor>(contents)?; + let (partition_name, remainder) = + split_slice(descriptor.body, descriptor.header.partition_name_len)?; + let (salt, remainder) = split_slice(remainder, descriptor.header.salt_len)?; + let (root_digest, _) = split_slice(remainder, descriptor.header.root_digest_len)?; + + // Extract the hash algorithm from the original raw header since the temporary + // byte-swapped header doesn't live past this function. + // The hash algorithm is a nul-terminated UTF-8 string which is identical in the raw + // and byteswapped headers. + let hash_algorithm = + CStr::from_bytes_until_nul(&descriptor.raw_header.hash_algorithm)?.to_str()?; + + Ok(Self { + dm_verity_version: descriptor.header.dm_verity_version, + image_size: descriptor.header.image_size, + tree_offset: descriptor.header.tree_offset, + tree_size: descriptor.header.tree_size, + data_block_size: descriptor.header.data_block_size, + hash_block_size: descriptor.header.hash_block_size, + fec_num_roots: descriptor.header.fec_num_roots, + fec_offset: descriptor.header.fec_offset, + fec_size: descriptor.header.fec_size, + hash_algorithm, + partition_name: from_utf8(partition_name)?, + salt, + root_digest, + flags: HashtreeDescriptorFlags(descriptor.header.flags), + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::DescriptorError; + use std::{fs, mem::size_of}; + + /// A valid descriptor that we've pre-generated as test data. + fn test_contents() -> Vec<u8> { + fs::read("testdata/hashtree_descriptor.bin").unwrap() + } + + #[test] + fn new_hashtree_descriptor_success() { + assert!(HashtreeDescriptor::new(&test_contents()).is_ok()); + } + + #[test] + fn new_hashtree_descriptor_too_short_header_fails() { + let bad_header_size = size_of::<AvbHashtreeDescriptor>() - 1; + assert_eq!( + HashtreeDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(), + DescriptorError::InvalidHeader + ); + } + + #[test] + fn new_hashtree_descriptor_too_short_contents_fails() { + // The last 2 bytes are padding, so we need to drop 3 bytes to trigger an error. + let bad_contents_size = test_contents().len() - 3; + assert_eq!( + HashtreeDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(), + DescriptorError::InvalidSize + ); + } +} diff --git a/rust/src/descriptor/mod.rs b/rust/src/descriptor/mod.rs new file mode 100644 index 0000000..488401e --- /dev/null +++ b/rust/src/descriptor/mod.rs @@ -0,0 +1,271 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Descriptor extraction and handling. +//! +//! Descriptors are information encoded into vbmeta images which can be +//! extracted from the resulting data after performing verification. + +extern crate alloc; + +mod chain; +mod commandline; +mod hash; +mod hashtree; +mod property; +mod util; + +use crate::VbmetaData; +use alloc::vec::Vec; +use avb_bindgen::{ + avb_descriptor_foreach, avb_descriptor_validate_and_byteswap, AvbDescriptor, AvbDescriptorTag, +}; +use core::{ + ffi::{c_void, FromBytesUntilNulError}, + mem::size_of, + slice, + str::Utf8Error, +}; + +pub use chain::{ChainPartitionDescriptor, ChainPartitionDescriptorFlags}; +pub use commandline::{KernelCommandlineDescriptor, KernelCommandlineDescriptorFlags}; +pub use hash::{HashDescriptor, HashDescriptorFlags}; +pub use hashtree::{HashtreeDescriptor, HashtreeDescriptorFlags}; +pub use property::PropertyDescriptor; + +/// A single descriptor. +#[derive(Debug, PartialEq, Eq)] +pub enum Descriptor<'a> { + /// Wraps `AvbPropertyDescriptor`. + Property(PropertyDescriptor<'a>), + /// Wraps `AvbHashtreeDescriptor`. + Hashtree(HashtreeDescriptor<'a>), + /// Wraps `AvbHashDescriptor`. + Hash(HashDescriptor<'a>), + /// Wraps `AvbKernelCmdlineDescriptor`. + KernelCommandline(KernelCommandlineDescriptor<'a>), + /// Wraps `AvbChainPartitionDescriptor`. + ChainPartition(ChainPartitionDescriptor<'a>), + /// Unknown descriptor type. + Unknown(&'a [u8]), +} + +/// Possible errors when extracting descriptors. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum DescriptorError { + /// libavb rejected the descriptor header. + InvalidHeader, + /// A value in the descriptor was invalid. + InvalidValue, + /// The descriptor claimed to be larger than the available data. + InvalidSize, + /// A field that was supposed to be valid UTF-8 was not. + InvalidUtf8, + /// Descriptor contents don't match what we expect. + InvalidContents, +} + +impl From<Utf8Error> for DescriptorError { + fn from(_: Utf8Error) -> Self { + Self::InvalidUtf8 + } +} + +impl From<FromBytesUntilNulError> for DescriptorError { + fn from(_: FromBytesUntilNulError) -> Self { + Self::InvalidContents + } +} + +/// `Result` type for `DescriptorError` errors. +pub type DescriptorResult<T> = Result<T, DescriptorError>; + +impl<'a> Descriptor<'a> { + /// Extracts the fully-typed descriptor from the generic `AvbDescriptor` header. + /// + /// # Arguments + /// * `raw_descriptor`: the raw `AvbDescriptor` pointing into the vbmeta image. + /// + /// # Returns + /// The fully-typed `Descriptor`, or `DescriptorError` if parsing the descriptor failed. + /// + /// # Safety + /// `raw_descriptor` must point to a valid `AvbDescriptor`, including the `num_bytes_following` + /// data contents, that lives at least as long as `'a`. + unsafe fn new(raw_descriptor: *const AvbDescriptor) -> DescriptorResult<Self> { + // Transform header to host-endian. + let mut descriptor = AvbDescriptor { + tag: 0, + num_bytes_following: 0, + }; + // SAFETY: both args point to valid `AvbDescriptor` objects. + if !unsafe { avb_descriptor_validate_and_byteswap(raw_descriptor, &mut descriptor) } { + return Err(DescriptorError::InvalidHeader); + } + + // Extract the descriptor header and contents bytes. The descriptor sub-type headers + // include the top-level header as the first member, so we need to grab the entire + // descriptor including the top-level header. + let num_bytes_following = descriptor + .num_bytes_following + .try_into() + .map_err(|_| DescriptorError::InvalidValue)?; + let total_size = size_of::<AvbDescriptor>() + .checked_add(num_bytes_following) + .ok_or(DescriptorError::InvalidValue)?; + + // SAFETY: `raw_descriptor` points to the header plus `num_bytes_following` bytes. + let contents = unsafe { slice::from_raw_parts(raw_descriptor as *const u8, total_size) }; + + match descriptor.tag.try_into() { + Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_PROPERTY) => { + Ok(Descriptor::Property(PropertyDescriptor::new(contents)?)) + } + Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASHTREE) => { + Ok(Descriptor::Hashtree(HashtreeDescriptor::new(contents)?)) + } + Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_HASH) => { + Ok(Descriptor::Hash(HashDescriptor::new(contents)?)) + } + Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_KERNEL_CMDLINE) => Ok( + Descriptor::KernelCommandline(KernelCommandlineDescriptor::new(contents)?), + ), + Ok(AvbDescriptorTag::AVB_DESCRIPTOR_TAG_CHAIN_PARTITION) => Ok( + Descriptor::ChainPartition(ChainPartitionDescriptor::new(contents)?), + ), + _ => Ok(Descriptor::Unknown(contents)), + } + } +} + +/// Returns a vector of descriptors extracted from the given vbmeta image. +/// +/// # Arguments +/// * `vbmeta`: the `VbmetaData` object to extract descriptors from. +/// +/// # Returns +/// The descriptors, or `DescriptorError` if any error occurred. +/// +/// # Safety +/// `vbmeta` must have been validated by `slot_verify()`. +pub(crate) unsafe fn get_descriptors(vbmeta: &VbmetaData) -> DescriptorResult<Vec<Descriptor>> { + let mut result = Ok(Vec::new()); + + // Use `avb_descriptor_foreach()` to grab all the descriptor pointers in `vmbeta.data()`. + // This implementation processes all the descriptors immediately, so that any error is + // detected here and working with descriptors can be error-free. + // + // SAFETY: + // * the caller ensures that `vbmeta` has been validated by `slot_verify()`, which satisfies + // the libavb `avb_vbmeta_image_verify()` requirement. + // * `avb_descriptor_foreach()` ensures the validity of each descriptor pointer passed to + // the `fill_descriptors_vec()` callback. + // * our lifetimes guarantee that the raw descriptor data in `vbmeta` will remain unchanged for + // the lifetime of the returned `Descriptor` objects. + // * the `user_data` param is a valid `DescriptorResult<Vec<Descriptor>>` with no other + // concurrent access. + unsafe { + // We can ignore the return value of this function since we use the passed-in `result` + // to convey success/failure as well as more detailed error info. + avb_descriptor_foreach( + vbmeta.data().as_ptr(), + vbmeta.data().len(), + Some(fill_descriptors_vec), + &mut result as *mut _ as *mut c_void, + ); + } + + result +} + +/// Adds the given descriptor to the `Vec` pointed to by `user_data`. +/// +/// Serves as a C callback for use with `avb_descriptor_foreach()`. +/// +/// # Returns +/// True on success, false on failure (which will stop iteration early). +/// +/// # Safety +/// * `descriptor` must point to a valid `AvbDescriptor`, including the `num_bytes_following` +/// data contents, which remains valid and unmodified for the lifetime of the `Descriptor` objects +/// in `user_data`. +/// * `user_data` must point to a valid `DescriptorResult<Vec<Descriptor>>` with no other concurrent +/// access. +unsafe extern "C" fn fill_descriptors_vec( + descriptor: *const AvbDescriptor, + user_data: *mut c_void, +) -> bool { + // SAFETY: `user_data` gives exclusive access to a valid `DescriptorResult<Vec<Descriptor>>`. + let result = unsafe { (user_data as *mut DescriptorResult<Vec<Descriptor>>).as_mut() }; + // We can always unwrap here because we never pass a NULL pointer as `user_data`. + let result = result.unwrap(); + + // SAFETY: caller ensures that `descriptor` points to a valid `AvbDescriptor` with header and + // body contents, which remains unmodified at least as long as the new `Descriptor`. + match unsafe { Descriptor::new(descriptor) } { + Ok(d) => { + // We can always unwrap here because this function will never be called with an error + // in `result`, since we stop iteration as soon as we encounter an error. + result.as_mut().unwrap().push(d); + true + } + Err(e) => { + // Set the error and stop iteration early. + *result = Err(e); + false + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn new_unknown_descriptor() { + // A fake descriptor which is valid but with an unknown tag. + let data: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, // tag = 0x42u64 (BE) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // num_bytes_following = 8u64 (BE) + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // fake contents + ]; + + // SAFETY: we've crafted a valid descriptor in `data`. + let descriptor = unsafe { Descriptor::new(data.as_ptr() as *const _) }.unwrap(); + + let contents = match descriptor { + Descriptor::Unknown(c) => c, + d => panic!("Expected Unknown descriptor, got {d:?}"), + }; + assert_eq!(data, contents); + } + + #[test] + fn new_invalid_descriptor_length_fails() { + // `avb_descriptor_validate_and_byteswap()` should detect and reject descriptors whose + // `num_bytes_following` is not 8-byte aligned. + let data: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, // tag = 0x42u64 (BE) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07, // num_bytes_following = 7u64 (BE) + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, // fake contents + ]; + + assert_eq!( + // SAFETY: we've created an invalid descriptor in a way that should be detected and + // fail safely without triggering any undefined behavior. + unsafe { Descriptor::new(data.as_ptr() as *const _) }.unwrap_err(), + DescriptorError::InvalidHeader + ); + } +} diff --git a/rust/src/descriptor/property.rs b/rust/src/descriptor/property.rs new file mode 100644 index 0000000..c97f48f --- /dev/null +++ b/rust/src/descriptor/property.rs @@ -0,0 +1,108 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Property descriptors. + +use super::{ + util::{parse_descriptor, split_slice, ValidateAndByteswap, ValidationFunc}, + DescriptorError, DescriptorResult, +}; +use avb_bindgen::{avb_property_descriptor_validate_and_byteswap, AvbPropertyDescriptor}; +use core::str::from_utf8; + +/// Checks that the first byte is nul and discards it. +/// Returns the remainder of `bytes` on success, or `DescriptorError` if the byte wasn't nul. +fn extract_nul(bytes: &[u8]) -> DescriptorResult<&[u8]> { + let (nul, remainder) = split_slice(bytes, 1)?; + match nul { + b"\0" => Ok(remainder), + _ => Err(DescriptorError::InvalidContents), + } +} + +/// Wraps an `AvbPropertyDescriptor` stored in a vbmeta image. +#[derive(Debug, PartialEq, Eq)] +pub struct PropertyDescriptor<'a> { + /// Key is always UTF-8. + pub key: &'a str, + + /// Value can be arbitrary bytes. + pub value: &'a [u8], +} + +// SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor type. +unsafe impl ValidateAndByteswap for AvbPropertyDescriptor { + const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> = + avb_property_descriptor_validate_and_byteswap; +} + +impl<'a> PropertyDescriptor<'a> { + /// Extract a `PropertyDescriptor` from the given descriptor contents. + /// + /// # Arguments + /// * `contents`: descriptor contents, including the header, in raw big-endian format. + /// + /// # Returns + /// The new descriptor, or `DescriptorError` if the given `contents` aren't a valid + /// `AvbPropertyDescriptor`. + pub(super) fn new(contents: &'a [u8]) -> DescriptorResult<Self> { + // Descriptor contains: header + key + nul + value + nul. + let descriptor = parse_descriptor::<AvbPropertyDescriptor>(contents)?; + let (key, remainder) = split_slice(descriptor.body, descriptor.header.key_num_bytes)?; + let remainder = extract_nul(remainder)?; + let (value, remainder) = split_slice(remainder, descriptor.header.value_num_bytes)?; + extract_nul(remainder)?; + + Ok(Self { + key: from_utf8(key)?, + value, + }) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use std::{fs, mem::size_of}; + + /// A valid descriptor that we've pre-generated as test data. + fn test_contents() -> Vec<u8> { + fs::read("testdata/property_descriptor.bin").unwrap() + } + + #[test] + fn new_property_descriptor_success() { + assert!(PropertyDescriptor::new(&test_contents()).is_ok()); + } + + #[test] + fn new_property_descriptor_too_short_header_fails() { + let bad_header_size = size_of::<AvbPropertyDescriptor>() - 1; + assert_eq!( + PropertyDescriptor::new(&test_contents()[..bad_header_size]).unwrap_err(), + DescriptorError::InvalidHeader + ); + } + + #[test] + fn new_property_descriptor_too_short_contents_fails() { + // The last 2 bytes are padding, so we need to drop 3 bytes to trigger an error. + let bad_contents_size = test_contents().len() - 3; + assert_eq!( + PropertyDescriptor::new(&test_contents()[..bad_contents_size]).unwrap_err(), + DescriptorError::InvalidSize + ); + } +} diff --git a/rust/src/descriptor/util.rs b/rust/src/descriptor/util.rs new file mode 100644 index 0000000..e4e23fd --- /dev/null +++ b/rust/src/descriptor/util.rs @@ -0,0 +1,178 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Descriptor utilities. + +use super::{DescriptorError, DescriptorResult}; +use zerocopy::{FromBytes, FromZeroes, Ref}; + +/// Splits `size` bytes off the front of `data`. +/// +/// This is a thin wrapper around `slice::split_at()` but it: +/// 1. Returns a `DescriptorError` rather than panicking if `data` is too small. +/// 2. Accepts a variety of `size` types since descriptors commonly use `u32` or `u64`. +/// +/// # Arguments +/// * `data`: descriptor data. +/// * `size`: the number of bytes to pull off the front. +/// +/// # Returns +/// A tuple containing (extracted_bytes, data_remainder) on success, or +/// `DescriptorError` if we couldn't get `size` bytes out of `data`. +pub(super) fn split_slice<T>(data: &[u8], size: T) -> DescriptorResult<(&[u8], &[u8])> +where + T: TryInto<usize>, +{ + let size = size.try_into().map_err(|_| DescriptorError::InvalidValue)?; + if size > data.len() { + Err(DescriptorError::InvalidSize) + } else { + Ok(data.split_at(size)) + } +} + +/// Function type for the `avb_*descriptor_validate_and_byteswap()` C functions. +pub(super) type ValidationFunc<T> = unsafe extern "C" fn(*const T, *mut T) -> bool; + +/// Trait to represent an `Avb*Descriptor` type which has an associated `ValidationFunc`. +/// +/// This allows the generic `parse_descriptor()` function to extract a descriptor header for any +/// descriptor type. +/// +/// To use, implement `ValidateAndByteSwap` on the `Avb*Descriptor` struct. +/// +/// # Safety +/// The function assigned to `VALIDATE_AND_BYTESWAP_FUNC` must be the libavb +/// `avb_*descriptor_validate_and_byteswap()` function corresponding to the descriptor implementing +/// this trait (e.g. `AvbHashDescriptor` -> `avb_hash_descriptor_validate_and_byteswap`). +pub(super) unsafe trait ValidateAndByteswap { + /// The specific libavb validation function for this descriptor type. + const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self>; +} + +/// A descriptor that has been extracted, validated, and byteswapped. +#[derive(Debug)] +pub(super) struct ParsedDescriptor<'a, T> { + /// The original raw (big-endian) header. + pub raw_header: &'a T, + /// A copy of the header in host-endian format. + pub header: T, + /// The descriptor body contents. + pub body: &'a [u8], +} + +/// Extracts the descriptor header from the given buffer. +/// +/// # Arguments +/// `data`: the descriptor contents in raw (big-endian) format. +/// +/// # Returns +/// A `ParsedDescriptor` on success, `DescriptorError` if `data` was too small or the header looks +/// invalid. +pub(super) fn parse_descriptor<T>(data: &[u8]) -> DescriptorResult<ParsedDescriptor<T>> +where + T: Default + FromZeroes + FromBytes + ValidateAndByteswap, +{ + let (raw_header, body) = + Ref::<_, T>::new_from_prefix(data).ok_or(DescriptorError::InvalidHeader)?; + let raw_header = raw_header.into_ref(); + + let mut header = T::default(); + // SAFETY: + // * all `VALIDATE_AND_BYTESWAP_FUNC` functions check the validity of the fields. + // * even if the data is corrupted somehow and is not detected by the validation logic, these + // functions never try to access memory outside of the header. + if !unsafe { T::VALIDATE_AND_BYTESWAP_FUNC(raw_header, &mut header) } { + return Err(DescriptorError::InvalidHeader); + } + + Ok(ParsedDescriptor { + raw_header, + header, + body, + }) +} + +#[cfg(test)] +mod tests { + use super::*; + use avb_bindgen::{avb_descriptor_validate_and_byteswap, AvbDescriptor, AvbHashDescriptor}; + use std::mem::size_of; + + #[test] + fn split_slice_with_various_size_types_succeeds() { + let data = &[1, 2, 3, 4]; + let expected = Ok((&data[..2], &data[2..])); + assert_eq!(split_slice(data, 2u32), expected); + assert_eq!(split_slice(data, 2u64), expected); + assert_eq!(split_slice(data, 2usize), expected); + } + + #[test] + fn split_slice_with_negative_size_fails() { + let data = &[1, 2, 3, 4]; + assert_eq!(split_slice(data, -1i32), Err(DescriptorError::InvalidValue)); + } + + #[test] + fn split_slice_with_size_overflow_fails() { + let data = &[1, 2, 3, 4]; + assert_eq!(split_slice(data, 5u32), Err(DescriptorError::InvalidSize)); + } + + // Enable `parse_descriptor()` on a generic `AvbDescriptor` of any sub-type. + // SAFETY: `VALIDATE_AND_BYTESWAP_FUNC` is the correct libavb validator for this descriptor. + unsafe impl ValidateAndByteswap for AvbDescriptor { + const VALIDATE_AND_BYTESWAP_FUNC: ValidationFunc<Self> = + avb_descriptor_validate_and_byteswap; + } + + // Hardcoded test descriptor of custom sub-type (tag = 42). + const TEST_DESCRIPTOR: &[u8] = &[ + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x42, // tag = 0x42u64 (BE) + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x08, // num_bytes_following = 8u64 (BE) + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, // fake contents + ]; + + #[test] + fn parse_descriptor_success() { + let descriptor = parse_descriptor::<AvbDescriptor>(TEST_DESCRIPTOR).unwrap(); + + // `assert_eq!()` cannot be used on `repr(packed)` struct fields. + assert!(descriptor.raw_header.tag == 0x42u64.to_be()); + assert!(descriptor.raw_header.num_bytes_following == 8u64.to_be()); + assert!(descriptor.header.tag == 0x42); + assert!(descriptor.header.num_bytes_following == 8); + assert_eq!(descriptor.body, &[1, 2, 3, 4, 5, 6, 7, 8]); + } + + #[test] + fn parse_descriptor_buffer_too_short_failure() { + let bad_length = size_of::<AvbDescriptor>() - 1; + assert_eq!( + parse_descriptor::<AvbDescriptor>(&TEST_DESCRIPTOR[..bad_length]).unwrap_err(), + DescriptorError::InvalidHeader + ); + } + + #[test] + fn parse_descriptor_wrong_type_failure() { + assert_eq!( + // `TEST_DESCRIPTOR` is not a valid `AvbHashDescriptor`, this should fail without + // triggering any undefined behavior. + parse_descriptor::<AvbHashDescriptor>(TEST_DESCRIPTOR).unwrap_err(), + DescriptorError::InvalidHeader + ); + } +} diff --git a/rust/src/error.rs b/rust/src/error.rs new file mode 100644 index 0000000..a5642c6 --- /dev/null +++ b/rust/src/error.rs @@ -0,0 +1,380 @@ +// Copyright 2022, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Error types used in libavb. +//! +//! There are a few advantages of providing these custom types rather than exposing the raw bindgen +//! enums directly: +//! * More idiomatic error handling +//! * C code defines a "status" enum that can contain either OK or an error, whereas Rust prefers +//! error-only enums to use with `Result<>` e.g. `Result<(), IoError>`. An "OK" status doesn't +//! make sense when used with `Result<>`. +//! * Better naming e.g. `IoError::Oom` vs the redundant `AvbIoResult::AVB_IO_RESULT_ERROR_OOM` +//! * We can implement traits such as `Display` for added convenience. + +// The naming scheme can be a bit confusing due to the re-use of "result" in a few places: +// * `Avb*Result`: raw libavb enums generated by bindgen, containing errors and "OK". Internal-only; +// library users should never have to use these types. +// * `*Error`: `Avb*Result` wrappers which only contain error conditions, not "OK". Should be +// wrapped in a Rust `Result<>` in public API. +// * `Result<T, *Error>`: top-level `Result<>` type used in this library's public API. + +use crate::SlotVerifyData; +use avb_bindgen::{AvbIOResult, AvbSlotVerifyResult, AvbVBMetaVerifyResult}; +use core::{fmt, str::Utf8Error}; + +/// `AvbSlotVerifyResult` error wrapper. +#[derive(Debug, PartialEq, Eq)] +pub enum SlotVerifyError<'a> { + /// `AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT` + InvalidArgument, + /// `AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA` + InvalidMetadata, + /// `AVB_SLOT_VERIFY_RESULT_ERROR_IO` + Io, + /// `AVB_SLOT_VERIFY_RESULT_ERROR_OOM` + Oom, + /// `AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED` + PublicKeyRejected, + /// `AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX` + RollbackIndex, + /// `AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION` + UnsupportedVersion, + /// `AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION` + /// + /// This verification error can contain the resulting `SlotVerifyData` if the + /// `AllowVerificationError` flag was passed into `slot_verify()`. + Verification(Option<SlotVerifyData<'a>>), + /// Unexpected internal error. This does not have a corresponding libavb error code. + Internal, +} + +/// `Result` type for `SlotVerifyError` errors. +pub type SlotVerifyResult<'a, T> = Result<T, SlotVerifyError<'a>>; + +/// `Result` type for `SlotVerifyError` errors without any `SlotVerifyData`. +/// +/// If the contained error will never hold a `SlotVerifyData`, this is easier to work with compared +/// to `SlotVerifyResult` due to the static lifetime bound. +pub type SlotVerifyNoDataResult<T> = SlotVerifyResult<'static, T>; + +impl<'a> SlotVerifyError<'a> { + /// Returns a copy of this error without any contained `SlotVerifyData`. + /// + /// This can simplify usage if the user doesn't care about the `SlotVerifyData` by turning the + /// current lifetime bound into `'static`. + pub fn without_verify_data(&self) -> SlotVerifyError<'static> { + match self { + Self::InvalidArgument => SlotVerifyError::InvalidArgument, + Self::InvalidMetadata => SlotVerifyError::InvalidMetadata, + Self::Io => SlotVerifyError::Io, + Self::Oom => SlotVerifyError::Oom, + Self::PublicKeyRejected => SlotVerifyError::PublicKeyRejected, + Self::RollbackIndex => SlotVerifyError::RollbackIndex, + Self::UnsupportedVersion => SlotVerifyError::UnsupportedVersion, + Self::Verification(_) => SlotVerifyError::Verification(None), + Self::Internal => SlotVerifyError::Internal, + } + } +} + +impl<'a> fmt::Display for SlotVerifyError<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::InvalidArgument => write!(f, "Invalid parameters"), + Self::InvalidMetadata => write!(f, "Invalid metadata"), + Self::Io => write!(f, "I/O error"), + Self::Oom => write!(f, "Unable to allocate memory"), + Self::PublicKeyRejected => write!(f, "Public key rejected or data not signed"), + Self::RollbackIndex => write!(f, "Rollback index violation"), + Self::UnsupportedVersion => write!(f, "Unsupported vbmeta version"), + Self::Verification(_) => write!(f, "Verification failure"), + Self::Internal => write!(f, "Internal error"), + } + } +} + +/// Converts a bindgen `AvbSlotVerifyResult` enum to a `SlotVerifyNoDataResult<>`, mapping +/// `AVB_SLOT_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding +/// `Err(SlotVerifyError)`. +/// +/// A `Verification` error returned here will always have a `None` `SlotVerifyData`; the data should +/// be added in later if it exists. +/// +/// 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 slot_verify_enum_to_result( + result: AvbSlotVerifyResult, +) -> SlotVerifyNoDataResult<()> { + match result { + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK => Ok(()), + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_ARGUMENT => { + Err(SlotVerifyError::InvalidArgument) + } + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_INVALID_METADATA => { + Err(SlotVerifyError::InvalidMetadata) + } + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_IO => Err(SlotVerifyError::Io), + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_OOM => Err(SlotVerifyError::Oom), + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_PUBLIC_KEY_REJECTED => { + Err(SlotVerifyError::PublicKeyRejected) + } + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_ROLLBACK_INDEX => { + Err(SlotVerifyError::RollbackIndex) + } + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_UNSUPPORTED_VERSION => { + Err(SlotVerifyError::UnsupportedVersion) + } + AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_VERIFICATION => { + Err(SlotVerifyError::Verification(None)) + } + } +} + +/// `AvbIOResult` error wrapper. +#[derive(Clone, Debug, PartialEq, Eq)] +pub enum IoError { + /// `AVB_IO_RESULT_ERROR_OOM` + Oom, + /// `AVB_IO_RESULT_ERROR_IO` + Io, + /// `AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION` + NoSuchPartition, + /// `AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION` + RangeOutsidePartition, + /// `AVB_IO_RESULT_ERROR_NO_SUCH_VALUE` + NoSuchValue, + /// `AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE` + InvalidValueSize, + /// `AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE`. Also contains the space that would be required. + InsufficientSpace(usize), + /// Custom error code to indicate that an optional callback method has not been implemented. + /// If this is returned from a required callback method, it will bubble up as an `Io` error. + NotImplemented, +} + +/// `Result` type for `IoError` errors. +pub type IoResult<T> = Result<T, IoError>; + +impl fmt::Display for IoError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::Oom => write!(f, "Unable to allocate memory"), + Self::Io => write!(f, "I/O error"), + Self::NoSuchPartition => write!(f, "No such partition exists"), + Self::RangeOutsidePartition => write!(f, "Range is outside the partition"), + Self::NoSuchValue => write!(f, "No such named persistent value"), + Self::InvalidValueSize => write!(f, "Invalid named persistent value size"), + Self::InsufficientSpace(size) => write!(f, "Buffer is too small (requires {})", size), + Self::NotImplemented => write!(f, "Function not implemented"), + } + } +} + +impl From<Utf8Error> for IoError { + fn from(_: Utf8Error) -> Self { + Self::Io + } +} + +// Converts our `IoError` to the bindgen `AvbIOResult` enum. +// +// Unlike `SlotVerifyError` which gets generated by libavb and passed to the caller, `IoError` is +// created by the user callbacks and passed back into libavb so we need to be able to convert in +// this direction as well. +impl From<IoError> for AvbIOResult { + fn from(error: IoError) -> Self { + match error { + IoError::Oom => AvbIOResult::AVB_IO_RESULT_ERROR_OOM, + IoError::Io => AvbIOResult::AVB_IO_RESULT_ERROR_IO, + IoError::NoSuchPartition => AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_PARTITION, + IoError::RangeOutsidePartition => { + AvbIOResult::AVB_IO_RESULT_ERROR_RANGE_OUTSIDE_PARTITION + } + IoError::NoSuchValue => AvbIOResult::AVB_IO_RESULT_ERROR_NO_SUCH_VALUE, + IoError::InvalidValueSize => AvbIOResult::AVB_IO_RESULT_ERROR_INVALID_VALUE_SIZE, + IoError::InsufficientSpace(_) => AvbIOResult::AVB_IO_RESULT_ERROR_INSUFFICIENT_SPACE, + // `NotImplemented` is internal to this library and doesn't have a libavb equivalent, + // convert it to the default I/O error. + IoError::NotImplemented => AvbIOResult::AVB_IO_RESULT_ERROR_IO, + } + } +} + +/// Converts an `IoResult<>` to the bindgen `AvbIOResult` enum. +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 { + /// `AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED` + NotSigned, + /// `AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER` + InvalidVbmetaHeader, + /// `AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION` + UnsupportedVersion, + /// `AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH` + HashMismatch, + /// `AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH` + SignatureMismatch, +} + +/// `Result` type for `VbmetaVerifyError` errors. +pub type VbmetaVerifyResult<T> = Result<T, VbmetaVerifyError>; + +impl fmt::Display for VbmetaVerifyError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Self::NotSigned => write!(f, "vbmeta is unsigned"), + Self::InvalidVbmetaHeader => write!(f, "invalid vbmeta header"), + Self::UnsupportedVersion => write!(f, "unsupported vbmeta version"), + Self::HashMismatch => write!(f, "vbmeta hash mismatch"), + Self::SignatureMismatch => write!(f, "vbmeta signature mismatch"), + } + } +} + +// Converts a bindgen `AvbVBMetaVerifyResult` enum to a `VbmetaVerifyResult<>`, mapping +// `AVB_VBMETA_VERIFY_RESULT_OK` to the Rust equivalent `Ok(())` and errors to the corresponding +// `Err(SlotVerifyError)`. +// +// 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 fn vbmeta_verify_enum_to_result(result: AvbVBMetaVerifyResult) -> VbmetaVerifyResult<()> { + match result { + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK => Ok(()), + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK_NOT_SIGNED => { + Err(VbmetaVerifyError::NotSigned) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_INVALID_VBMETA_HEADER => { + Err(VbmetaVerifyError::InvalidVbmetaHeader) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_UNSUPPORTED_VERSION => { + Err(VbmetaVerifyError::UnsupportedVersion) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH => { + Err(VbmetaVerifyError::HashMismatch) + } + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_SIGNATURE_MISMATCH => { + Err(VbmetaVerifyError::SignatureMismatch) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn display_slot_verify_error() { + // The actual error message can change as needed, the point of the test is just to make sure + // the fmt::Display trait is properly implemented. + assert_eq!( + format!("{}", SlotVerifyError::Verification(None)), + "Verification failure" + ); + } + + #[test] + fn convert_slot_verify_enum_to_result() { + assert!(matches!( + slot_verify_enum_to_result(AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_OK), + Ok(()) + )); + assert!(matches!( + slot_verify_enum_to_result(AvbSlotVerifyResult::AVB_SLOT_VERIFY_RESULT_ERROR_IO), + Err(SlotVerifyError::Io) + )); + } + + #[test] + fn display_io_error() { + // The actual error message can change as needed, the point of the test is just to make sure + // the fmt::Display trait is properly implemented. + assert_eq!( + format!("{}", IoError::NoSuchPartition), + "No such partition exists" + ); + } + + #[test] + fn convert_io_enum_to_result() { + // 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_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] + fn convert_io_result_to_enum() { + assert_eq!(result_to_io_enum(Ok(())), AvbIOResult::AVB_IO_RESULT_OK); + assert_eq!( + result_to_io_enum(Err(IoError::Io)), + AvbIOResult::AVB_IO_RESULT_ERROR_IO + ); + } + + #[test] + fn display_vmbeta_verify_error() { + // The actual error message can change as needed, the point of the test is just to make sure + // the fmt::Display trait is properly implemented. + assert_eq!( + format!("{}", VbmetaVerifyError::NotSigned), + "vbmeta is unsigned" + ); + } + + #[test] + fn convert_vbmeta_verify_enum_to_result() { + assert_eq!( + vbmeta_verify_enum_to_result(AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_OK), + Ok(()) + ); + assert_eq!( + vbmeta_verify_enum_to_result( + AvbVBMetaVerifyResult::AVB_VBMETA_VERIFY_RESULT_HASH_MISMATCH + ), + Err(VbmetaVerifyError::HashMismatch) + ); + } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..99962ab --- /dev/null +++ b/rust/src/lib.rs @@ -0,0 +1,53 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Rust libavb. +//! +//! This library wraps the libavb C code with safe Rust APIs. This does not materially affect the +//! safety of the library itself, since the internal implementation is still C. The goal here is +//! instead to provide a simple way to use libavb from Rust, in order to make Rust a more +//! appealing option for code that may want to use libavb such as bootloaders. +//! +//! This library is [no_std] for portability. + +// ANDROID: Use std to allow building as a dylib. +// This condition lets us make the hack to add a dependency on std for the +// panic_handler and eh_personality conditional on actually building a dylib. +#![cfg_attr(not(any(test, android_dylib)), no_std)] + +mod cert; +mod descriptor; +mod error; +mod ops; +mod verify; + +pub use cert::{ + 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, + DescriptorResult, HashDescriptor, HashDescriptorFlags, HashtreeDescriptor, + HashtreeDescriptorFlags, KernelCommandlineDescriptor, KernelCommandlineDescriptorFlags, + PropertyDescriptor, +}; +pub use error::{ + IoError, IoResult, SlotVerifyError, SlotVerifyNoDataResult, SlotVerifyResult, + VbmetaVerifyError, VbmetaVerifyResult, +}; +pub use ops::{Ops, PublicKeyForPartitionInfo}; +pub use verify::{ + slot_verify, HashtreeErrorMode, PartitionData, SlotVerifyData, SlotVerifyFlags, VbmetaData, +}; diff --git a/rust/src/ops.rs b/rust/src/ops.rs new file mode 100644 index 0000000..d8c1b16 --- /dev/null +++ b/rust/src/ops.rs @@ -0,0 +1,1343 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! User callback APIs. +//! +//! This module is responsible for bridging the user-implemented callbacks so that they can be +//! written in safe Rust but libavb can call them from C. + +extern crate alloc; + +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}, + marker::PhantomPinned, + pin::Pin, + ptr, slice, +}; +#[cfg(feature = "uuid")] +use uuid::Uuid; + +/// Base implementation-provided callbacks for verification. +/// +/// See libavb `AvbOps` for more complete documentation. +/// +/// # Lifetimes +/// The trait lifetime `'a` indicates the lifetime of any preloaded partition data. +/// +/// Preloading partitions is an optional feature which allows libavb to use data already loaded to +/// RAM rather than allocating memory itself and loading data from disk. Preloading changes the +/// data ownership model so that the verification result borrows this existing data rather than +/// allocating and owning the data itself. Because of this borrow, we need the lifetime here to +/// ensure that the underlying data outlives the verification result object. +/// +/// If `get_preloaded_partition()` is left unimplemented, all data is loaded and owned by the +/// verification result rather than borrowed, and this trait lifetime can be `'static`. +pub trait Ops<'a> { + /// Reads data from the requested partition on disk. + /// + /// # Arguments + /// * `partition`: partition name to read from. + /// * `offset`: offset in bytes within the partition to read from; a positive value indicates an + /// offset from the partition start, a negative value indicates a backwards offset + /// from the partition end. + /// * `buffer`: buffer to read data into. + /// + /// # Returns + /// The number of bytes actually read into `buffer` or an `IoError`. Reading less than + /// `buffer.len()` bytes is only allowed if the end of the partition was reached. + fn read_from_partition( + &mut self, + partition: &CStr, + offset: i64, + buffer: &mut [u8], + ) -> IoResult<usize>; + + /// Returns a reference to preloaded partition contents. + /// + /// This is an optional optimization if a partition has already been loaded to provide libavb + /// with a reference to the data rather than copying it as `read_from_partition()` would. + /// + /// May be left unimplemented if preloaded partitions are not used. + /// + /// # Arguments + /// * `partition`: partition name to read from. + /// + /// # Returns + /// * A reference to the entire partition contents if the partition has been preloaded. + /// * `Err<IoError::NotImplemented>` if the requested partition has not been preloaded; + /// verification will next attempt to load the partition via `read_from_partition()`. + /// * Any other `Err<IoError>` if an error occurred; verification will exit immediately. + fn get_preloaded_partition(&mut self, _partition: &CStr) -> IoResult<&'a [u8]> { + Err(IoError::NotImplemented) + } + + /// Checks if the given public key is valid for vbmeta image signing. + /// + /// If using libavb_cert, this should forward to `cert_validate_vbmeta_public_key()`. + /// + /// # Arguments + /// * `public_key`: the public key. + /// * `public_key_metadata`: public key metadata set by the `--public_key_metadata` arg in + /// `avbtool`, or None if no metadata was provided. + /// + /// # Returns + /// True if the given key is valid, false if it is not, `IoError` on error. + fn validate_vbmeta_public_key( + &mut self, + public_key: &[u8], + public_key_metadata: Option<&[u8]>, + ) -> IoResult<bool>; + + /// Reads the rollback index at the given location. + /// + /// # Arguments + /// * `rollback_index_location`: the rollback location. + /// + /// # Returns + /// The rollback index at this location or `IoError` on error. + fn read_rollback_index(&mut self, rollback_index_location: usize) -> IoResult<u64>; + + /// Writes the rollback index at the given location. + /// + /// This API is never actually used by libavb; the purpose of having it here is to group it + /// with `read_rollback_index()` and indicate to the implementation that it is responsible + /// for providing this functionality. However, it's up to the implementation to call this + /// function at the proper time after verification, which is a device-specific decision that + /// depends on things like the A/B strategy. See the libavb documentation for more information. + /// + /// # Arguments + /// * `rollback_index_location`: the rollback location. + /// * `index`: the rollback index to write. + /// + /// # Returns + /// Unit on success or `IoError` on error. + fn write_rollback_index(&mut self, rollback_index_location: usize, index: u64) -> IoResult<()>; + + /// Returns the device unlock state. + /// + /// # Returns + /// True if the device is unlocked, false if locked, `IoError` on error. + fn read_is_device_unlocked(&mut self) -> IoResult<bool>; + + /// Returns the GUID of the requested partition. + /// + /// This is only necessary if the kernel commandline requires GUID substitution, and is omitted + /// from the library by default to avoid unnecessary dependencies. To implement: + /// 1. Enable the `uuid` feature during compilation + /// 2. Provide the [`uuid` crate](https://docs.rs/uuid/latest/uuid/) dependency + /// + /// # Arguments + /// * `partition`: partition name. + /// + /// # Returns + /// The partition GUID or `IoError` on error. + #[cfg(feature = "uuid")] + fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid>; + + /// Returns the size of the requested partition. + /// + /// # Arguments + /// * `partition`: partition name. + /// + /// # Returns + /// The partition size in bytes or `IoError` on error. + fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64>; + + /// Reads the requested persistent value. + /// + /// This is only necessary if using persistent digests or the "managed restart and EIO" + /// hashtree verification mode; if verification is not using these features, this function will + /// never be called. + /// + /// # Arguments + /// * `name`: persistent value name. + /// * `value`: buffer to read persistent value into; if too small to hold the persistent value, + /// `IoError::InsufficientSpace` should be returned and this function will be called + /// again with an appropriately-sized buffer. This may be an empty slice if the + /// caller only wants to query the persistent value size. + /// + /// # Returns + /// * The number of bytes written into `value` on success. + /// * `IoError::NoSuchValue` if `name` is not a known persistent value. + /// * `IoError::InsufficientSpace` with the required size if the `value` buffer is too small. + /// * Any other `IoError` on failure. + fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize>; + + /// Writes the requested persistent value. + /// + /// This is only necessary if using persistent digests or the "managed restart and EIO" + /// hashtree verification mode; if verification is not using these features, this function will + /// never be called. + /// + /// # Arguments + /// * `name`: persistent value name. + /// * `value`: bytes to write as the new value. + /// + /// # Returns + /// * Unit on success. + /// * `IoError::NoSuchValue` if `name` is not a supported persistent value. + /// * `IoError::InvalidValueSize` if `value` is too large to save as a persistent value. + /// * Any other `IoError` on failure. + fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()>; + + /// Erases the requested persistent value. + /// + /// This is only necessary if using persistent digests or the "managed restart and EIO" + /// hashtree verification mode; if verification is not using these features, this function will + /// never be called. + /// + /// If the requested persistent value is already erased, this function is a no-op and should + /// return `Ok(())`. + /// + /// # Arguments + /// * `name`: persistent value name. + /// + /// # Returns + /// * Unit on success. + /// * `IoError::NoSuchValue` if `name` is not a supported persistent value. + /// * Any other `IoError` on failure. + fn erase_persistent_value(&mut self, name: &CStr) -> IoResult<()>; + + /// Checks if the given public key is valid for the given partition. + /// + /// This is only used if the "no vbmeta" verification flag is passed, meaning the partitions + /// to verify have an embedded vbmeta image rather than locating it in a separate vbmeta + /// partition. If this flag is not used, the `validate_vbmeta_public_key()` callback is used + /// instead, and this function will never be called. + /// + /// If using libavb_cert for `partition`, this should forward to + /// `cert_validate_vbmeta_public_key()`. + /// + /// # Arguments + /// * `partition`: partition name. + /// * `public_key`: the public key. + /// * `public_key_metadata`: public key metadata set by the `--public_key_metadata` arg in + /// `avbtool`, or None if no metadata was provided. + /// + /// # Returns + /// On success, returns a `PublicKeyForPartitionInfo` object indicating whether the given + /// key is trusted and its rollback index location. + /// + /// On failure, returns an error. + fn validate_public_key_for_partition( + &mut self, + partition: &CStr, + public_key: &[u8], + public_key_metadata: Option<&[u8]>, + ) -> IoResult<PublicKeyForPartitionInfo>; + + /// Returns the libavb_cert certificate ops if supported. + /// + /// The libavb_cert extension provides some additional key management and authentication support + /// APIs, see the cert module documentation for more info. + /// + /// The default implementation returns `None` to disable cert APIs. + /// + /// 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> { + None + } +} + +/// Info returned from `validate_public_key_for_partition()`. +#[derive(Clone, Copy, Debug)] +pub struct PublicKeyForPartitionInfo { + /// Whether the key is trusted for the given partition.. + pub trusted: bool, + /// The rollback index to use for the given partition. + pub rollback_index_location: u32, +} + +/// Provides the logic to bridge between the libavb C ops and our Rust ops. +/// +/// This struct internally owns the C ops structs, and borrows ownership of the Rust `Ops`. This +/// allows us to translate in both directions, from Rust -> C to call into libavb and then back +/// from C -> Rust when servicing the callbacks. +/// +/// The general control flow look like this: +/// +/// ```ignore +/// user calls a Rust API with their `Ops` { +/// we create `OpsBridge` wrapping `Ops` and the C structs +/// we call into C libavb API { +/// libavb makes C ops callback { +/// we retrieve `OpsBridge` from the callback `user_data` param +/// we make the corresponding Rust `Ops` callback +/// } +/// ... libavb makes more callbacks as needed ... +/// } +/// } +/// ``` +/// +/// # Lifetimes +/// * `'o`: lifetime of the `Ops` object +/// * `'p`: lifetime of any preloaded data provided by `Ops` +pub(crate) struct OpsBridge<'o, 'p> { + /// C `AvbOps` holds a raw pointer to the `OpsBridge` so we can retrieve it during callbacks. + avb_ops: AvbOps, + /// When using libavb_cert, C `AvbOps`/`AvbCertOps` hold circular pointers to each other. + cert_ops: AvbCertOps, + /// Rust `Ops` implementation, which may also provide Rust `CertOps`. + rust_ops: &'o mut dyn Ops<'p>, + /// Remove the `Unpin` trait to indicate this type has address-sensitive state. + _pin: PhantomPinned, +} + +impl<'o, 'p> OpsBridge<'o, 'p> { + pub(crate) fn new(ops: &'o mut dyn Ops<'p>) -> Self { + Self { + avb_ops: AvbOps { + user_data: ptr::null_mut(), // Set at the time of use. + ab_ops: ptr::null_mut(), // Deprecated, no need to support. + cert_ops: ptr::null_mut(), // Set at the time of use. + read_from_partition: Some(read_from_partition), + get_preloaded_partition: Some(get_preloaded_partition), + write_to_partition: None, // Not needed, only used for deprecated A/B. + validate_vbmeta_public_key: Some(validate_vbmeta_public_key), + read_rollback_index: Some(read_rollback_index), + write_rollback_index: Some(write_rollback_index), + read_is_device_unlocked: Some(read_is_device_unlocked), + get_unique_guid_for_partition: Some(get_unique_guid_for_partition), + get_size_of_partition: Some(get_size_of_partition), + read_persistent_value: Some(read_persistent_value), + write_persistent_value: Some(write_persistent_value), + validate_public_key_for_partition: Some(validate_public_key_for_partition), + }, + cert_ops: AvbCertOps { + ops: ptr::null_mut(), // Set at the time of use. + 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, + } + } + + /// Initializes and returns the C `AvbOps` structure from an `OpsBridge`. + /// + /// If the contained `Ops` supports `CertOps`, the returned `AvbOps` will also be configured + /// properly for libavb_cert. + /// + /// Pinning is necessary here because the returned `AvbOps` contains pointers into `self`, so + /// we cannot allow `self` to subsequently move or else the pointers would become invalid. + /// + /// # Returns + /// The C `AvbOps` struct to make libavb calls with. + pub(crate) fn init_and_get_c_ops<'a>(self: Pin<&'a mut Self>) -> &'a mut AvbOps { + // SAFETY: we do not move out of `self_mut`, but only set pointers to pinned addresses. + let self_mut = unsafe { self.get_unchecked_mut() }; + + // Set the C `user_data` to point back to us so we can retrieve ourself in callbacks. + self_mut.avb_ops.user_data = self_mut as *mut _ as *mut _; + + // If the `Ops` supports certs, set up the necessary additional pointer tracking. + if self_mut.rust_ops.cert_ops().is_some() { + self_mut.avb_ops.cert_ops = &mut self_mut.cert_ops; + self_mut.cert_ops.ops = &mut self_mut.avb_ops; + } + + &mut self_mut.avb_ops + } +} + +/// Extracts the user-provided `Ops` from a raw `AvbOps`. +/// +/// This function is used in libavb callbacks to bridge libavb's raw C `AvbOps` struct to our Rust +/// implementation. +/// +/// # Arguments +/// * `avb_ops`: The raw `AvbOps` pointer used by libavb. +/// +/// # Returns +/// The Rust `Ops` extracted from `avb_ops.user_data`. +/// +/// # Safety +/// * only call this function on an `AvbOps` created via `OpsBridge` +/// * drop all references to the returned `Ops` and preloaded data before returning control to +/// libavb or calling this function again +/// +/// In practice, these conditions are met since we call this at most once in each callback +/// to extract the `Ops`, and drop the references at callback completion. +/// +/// # Lifetimes +/// * `'o`: lifetime of the `Ops` object +/// * `'p`: lifetime of any preloaded data provided by `Ops` +/// +/// It's difficult to accurately provide the lifetimes when calling this function, since we are in +/// a C callback which provides no lifetime information in the args. We solve this in the safety +/// requirements by requiring the caller to drop both references before returning, which is always +/// a subset of the actual object lifetimes as the objects must remain valid while libavb is +/// actively using them: +/// +/// ```ignore +/// ops/preloaded lifetime { // Actual 'o/'p start +/// call into libavb { +/// libavb callbacks { +/// as_ops() // as_ops() 'o/'p start +/// } // as_ops() 'o/'p end +/// } +/// } // Actual 'o/'p end +/// ``` +unsafe fn as_ops<'o, 'p>(avb_ops: *mut AvbOps) -> IoResult<&'o mut dyn Ops<'p>> { + // SAFETY: we created this AvbOps object and passed it to libavb so we know it meets all + // the criteria for `as_mut()`. + let avb_ops = unsafe { avb_ops.as_mut() }.ok_or(IoError::Io)?; + // Cast the void* `user_data` back to a OpsBridge*. + let bridge = avb_ops.user_data as *mut OpsBridge; + // SAFETY: we created this OpsBridge object and passed it to libavb so we know it meets all + // the criteria for `as_mut()`. + 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() { + true => Err(IoError::Io), + false => Ok(()), + } +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn read_from_partition( + ops: *mut AvbOps, + partition: *const c_char, + offset: i64, + num_bytes: usize, + buffer: *mut c_void, + out_num_read: *mut usize, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_read_from_partition( + ops, + partition, + offset, + num_bytes, + buffer, + out_num_read, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `partition` must adhere to the requirements of `CStr::from_ptr()`. +/// * `buffer` must adhere to the requirements of `slice::from_raw_parts_mut()`. +/// * `out_num_read` must adhere to the requirements of `ptr::write()`. +unsafe fn try_read_from_partition( + ops: *mut AvbOps, + partition: *const c_char, + offset: i64, + num_bytes: usize, + buffer: *mut c_void, + out_num_read: *mut usize, +) -> IoResult<()> { + check_nonnull(partition)?; + check_nonnull(buffer)?; + check_nonnull(out_num_read)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_num_read`. + unsafe { ptr::write(out_num_read, 0) }; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated and nul-terminated `partition`. + // * the string contents are not modified while the returned `&CStr` exists. + // * the returned `&CStr` is not held past the scope of this callback. + let partition = unsafe { CStr::from_ptr(partition) }; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `buffer` 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 buffer = unsafe { slice::from_raw_parts_mut(buffer as *mut u8, num_bytes) }; + + let bytes_read = ops.read_from_partition(partition, offset, buffer)?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_num_read`. + unsafe { ptr::write(out_num_read, bytes_read) }; + Ok(()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn get_preloaded_partition( + ops: *mut AvbOps, + partition: *const c_char, + num_bytes: usize, + out_pointer: *mut *mut u8, + out_num_bytes_preloaded: *mut usize, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_get_preloaded_partition( + ops, + partition, + num_bytes, + out_pointer, + out_num_bytes_preloaded, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `partition` must adhere to the requirements of `CStr::from_ptr()`. +/// * `out_pointer` and `out_num_bytes_preloaded` must adhere to the requirements of `ptr::write()`. +/// * `out_pointer` will become an alias to the `ops` preloaded partition data, so the preloaded +/// data must remain valid and unmodified while `out_pointer` exists. +unsafe fn try_get_preloaded_partition( + ops: *mut AvbOps, + partition: *const c_char, + num_bytes: usize, + out_pointer: *mut *mut u8, + out_num_bytes_preloaded: *mut usize, +) -> IoResult<()> { + check_nonnull(partition)?; + check_nonnull(out_pointer)?; + check_nonnull(out_num_bytes_preloaded)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointers are non-NULL. + // * libavb gives us properly-aligned and sized `out` vars. + unsafe { + ptr::write(out_pointer, ptr::null_mut()); + ptr::write(out_num_bytes_preloaded, 0); + } + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated and nul-terminated `partition`. + // * the string contents are not modified while the returned `&CStr` exists. + // * the returned `&CStr` is not held past the scope of this callback. + let partition = unsafe { CStr::from_ptr(partition) }; + + match ops.get_preloaded_partition(partition) { + // SAFETY: + // * we've checked that the pointers are non-NULL. + // * libavb gives us properly-aligned and sized `out` vars. + Ok(contents) => unsafe { + ptr::write( + out_pointer, + // Warning: we are casting an immutable &[u8] to a mutable *u8. If libavb actually + // modified these contents this could cause undefined behavior, but it just reads. + // TODO: can we change the libavb API to take a const*? + contents.as_ptr() as *mut u8, + ); + ptr::write( + out_num_bytes_preloaded, + // Truncate here if necessary, we may have more preloaded data than libavb needs. + min(contents.len(), num_bytes), + ); + }, + // No-op if this partition is not preloaded, we've already reset the out variables to + // indicate preloaded data is not available. + Err(IoError::NotImplemented) => (), + Err(e) => return Err(e), + }; + Ok(()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn validate_vbmeta_public_key( + ops: *mut AvbOps, + public_key_data: *const u8, + public_key_length: usize, + public_key_metadata: *const u8, + public_key_metadata_length: usize, + out_is_trusted: *mut bool, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_validate_vbmeta_public_key( + ops, + public_key_data, + public_key_length, + public_key_metadata, + public_key_metadata_length, + out_is_trusted, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `public_key_*` args must adhere to the requirements of `slice::from_raw_parts()`. +/// * `out_is_trusted` must adhere to the requirements of `ptr::write()`. +unsafe fn try_validate_vbmeta_public_key( + ops: *mut AvbOps, + public_key_data: *const u8, + public_key_length: usize, + public_key_metadata: *const u8, + public_key_metadata_length: usize, + out_is_trusted: *mut bool, +) -> IoResult<()> { + check_nonnull(public_key_data)?; + check_nonnull(out_is_trusted)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_is_trusted`. + unsafe { ptr::write(out_is_trusted, false) }; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `public_key_data` with size `public_key_length`. + // * we only access the contents via the returned slice. + // * the returned slice is not held past the scope of this callback. + let public_key = unsafe { slice::from_raw_parts(public_key_data, public_key_length) }; + let metadata = check_nonnull(public_key_metadata).ok().map( + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `public_key_metadata` with size + // `public_key_metadata_length`. + // * we only access the contents via the returned slice. + // * the returned slice is not held past the scope of this callback. + |_| unsafe { slice::from_raw_parts(public_key_metadata, public_key_metadata_length) }, + ); + + let trusted = ops.validate_vbmeta_public_key(public_key, metadata)?; + + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_is_trusted`. + unsafe { ptr::write(out_is_trusted, trusted) }; + Ok(()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn read_rollback_index( + ops: *mut AvbOps, + rollback_index_location: usize, + out_rollback_index: *mut u64, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_read_rollback_index( + ops, + rollback_index_location, + out_rollback_index, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `out_rollback_index` must adhere to the requirements of `ptr::write()`. +unsafe fn try_read_rollback_index( + ops: *mut AvbOps, + rollback_index_location: usize, + out_rollback_index: *mut u64, +) -> IoResult<()> { + check_nonnull(out_rollback_index)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_rollback_index`. + unsafe { ptr::write(out_rollback_index, 0) }; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + let index = ops.read_rollback_index(rollback_index_location)?; + + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_rollback_index`. + unsafe { ptr::write(out_rollback_index, index) }; + Ok(()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn write_rollback_index( + ops: *mut AvbOps, + rollback_index_location: usize, + rollback_index: u64, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_write_rollback_index( + ops, + rollback_index_location, + rollback_index, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +unsafe fn try_write_rollback_index( + ops: *mut AvbOps, + rollback_index_location: usize, + rollback_index: u64, +) -> IoResult<()> { + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + ops.write_rollback_index(rollback_index_location, rollback_index) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn read_is_device_unlocked( + ops: *mut AvbOps, + out_is_unlocked: *mut bool, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { result_to_io_enum(try_read_is_device_unlocked(ops, out_is_unlocked)) } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `out_is_unlocked` must adhere to the requirements of `ptr::write()`. +unsafe fn try_read_is_device_unlocked( + ops: *mut AvbOps, + out_is_unlocked: *mut bool, +) -> IoResult<()> { + check_nonnull(out_is_unlocked)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_is_unlocked`. + unsafe { ptr::write(out_is_unlocked, false) }; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + let unlocked = ops.read_is_device_unlocked()?; + + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_is_unlocked`. + unsafe { ptr::write(out_is_unlocked, unlocked) }; + Ok(()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn get_unique_guid_for_partition( + ops: *mut AvbOps, + partition: *const c_char, + guid_buf: *mut c_char, + guid_buf_size: usize, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_get_unique_guid_for_partition( + ops, + partition, + guid_buf, + guid_buf_size, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// When the `uuid` feature is not enabled, this doesn't call into the user ops at all and instead +/// gives the empty string for all partitions. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `partition` must adhere to the requirements of `CStr::from_ptr()`. +/// * `guid_buf` must adhere to the requirements of `slice::from_raw_parts_mut()`. +unsafe fn try_get_unique_guid_for_partition( + #[allow(unused_variables)] ops: *mut AvbOps, + #[allow(unused_variables)] partition: *const c_char, + guid_buf: *mut c_char, + guid_buf_size: usize, +) -> IoResult<()> { + check_nonnull(guid_buf)?; + + // On some architectures `c_char` is `u8`, and on others `i8`. We make sure it's `u8` here + // since that's what `CStr::to_bytes_with_nul()` always provides. + #[allow(clippy::unnecessary_cast)] + let guid_buf = guid_buf as *mut u8; + + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `guid_buf` with size `guid_buf_size`. + // * we only access the contents via the returned slice. + // * the returned slice is not held past the scope of this callback. + let buffer = unsafe { slice::from_raw_parts_mut(guid_buf, guid_buf_size) }; + + // Initialize the output buffer to the empty string. + // + // When the `uuid` feature is not selected, the user doesn't need commandline GUIDs but libavb + // may still attempt to inject the `vmbeta` or `boot` partition GUIDs into the commandline, + // depending on the verification settings. In order to satisfy libavb's requirements we must: + // * write a nul-terminated string to avoid undefined behavior (empty string is sufficient) + // * return `Ok(())` or verification will fail + if buffer.is_empty() { + return Err(IoError::Oom); + } + buffer[0] = b'\0'; + + #[cfg(feature = "uuid")] + { + check_nonnull(partition)?; + + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated and nul-terminated `partition`. + // * the string contents are not modified while the returned `&CStr` exists. + // * the returned `&CStr` is not held past the scope of this callback. + let partition = unsafe { CStr::from_ptr(partition) }; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + let guid = ops.get_unique_guid_for_partition(partition)?; + + // Write the UUID string to a uuid buffer which is guaranteed to be large enough, then use + // `CString` to apply nul-termination. + // This does allocate memory, but it's short-lived and discarded as soon as we copy the + // properly-terminated string back to the buffer. + let mut encode_buffer = Uuid::encode_buffer(); + let guid_str = guid.as_hyphenated().encode_lower(&mut encode_buffer); + let guid_cstring = alloc::ffi::CString::new(guid_str.as_bytes()).or(Err(IoError::Io))?; + let guid_bytes = guid_cstring.to_bytes_with_nul(); + + if buffer.len() < guid_bytes.len() { + // This would indicate some internal error - the uuid library needs more + // space to print the UUID string than libavb provided. + return Err(IoError::Oom); + } + buffer[..guid_bytes.len()].copy_from_slice(guid_bytes); + } + + Ok(()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn get_size_of_partition( + ops: *mut AvbOps, + partition: *const c_char, + out_size_num_bytes: *mut u64, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_get_size_of_partition( + ops, + partition, + out_size_num_bytes, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `partition` must adhere to the requirements of `CStr::from_ptr()`. +/// * `out_size_num_bytes` must adhere to the requirements of `ptr::write()`. +unsafe fn try_get_size_of_partition( + ops: *mut AvbOps, + partition: *const c_char, + out_size_num_bytes: *mut u64, +) -> IoResult<()> { + check_nonnull(partition)?; + check_nonnull(out_size_num_bytes)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_size_num_bytes`. + unsafe { ptr::write(out_size_num_bytes, 0) }; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated and nul-terminated `partition`. + // * the string contents are not modified while the returned `&CStr` exists. + // * the returned `&CStr` is not held past the scope of this callback. + let partition = unsafe { CStr::from_ptr(partition) }; + let size = ops.get_size_of_partition(partition)?; + + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_size_num_bytes`. + unsafe { ptr::write(out_size_num_bytes, size) }; + Ok(()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn read_persistent_value( + ops: *mut AvbOps, + name: *const c_char, + buffer_size: usize, + out_buffer: *mut u8, + out_num_bytes_read: *mut usize, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_read_persistent_value( + ops, + name, + buffer_size, + out_buffer, + out_num_bytes_read, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `name` must adhere to the requirements of `CStr::from_ptr()`. +/// * `out_buffer` must adhere to the requirements of `slice::from_raw_parts_mut()`. +/// * `out_num_bytes_read` must adhere to the requirements of `ptr::write()`. +unsafe fn try_read_persistent_value( + ops: *mut AvbOps, + name: *const c_char, + buffer_size: usize, + out_buffer: *mut u8, + out_num_bytes_read: *mut usize, +) -> IoResult<()> { + check_nonnull(name)?; + check_nonnull(out_num_bytes_read)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_num_bytes_read`. + unsafe { ptr::write(out_num_bytes_read, 0) }; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated and nul-terminated `name`. + // * the string contents are not modified while the returned `&CStr` exists. + // * the returned `&CStr` is not held past the scope of this callback. + let name = unsafe { CStr::from_ptr(name) }; + let mut empty: [u8; 0] = []; + let value = match out_buffer.is_null() { + // NULL buffer => empty slice, used to just query the value size. + true => &mut empty, + false => { + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_buffer` with size `buffer_size`. + // * we only access the contents via the returned slice. + // * the returned slice is not held past the scope of this callback. + unsafe { slice::from_raw_parts_mut(out_buffer, buffer_size) } + } + }; + + let result = ops.read_persistent_value(name, value); + // On success or insufficient space we need to write the property size back. + if let Ok(size) | Err(IoError::InsufficientSpace(size)) = result { + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `out_num_bytes_read`. + unsafe { ptr::write(out_num_bytes_read, size) }; + }; + // We've written the size back and can drop it now. + result.map(|_| ()) +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn write_persistent_value( + ops: *mut AvbOps, + name: *const c_char, + value_size: usize, + value: *const u8, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { result_to_io_enum(try_write_persistent_value(ops, name, value_size, value)) } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `name` must adhere to the requirements of `CStr::from_ptr()`. +/// * `out_buffer` must adhere to the requirements of `slice::from_raw_parts()`. +unsafe fn try_write_persistent_value( + ops: *mut AvbOps, + name: *const c_char, + value_size: usize, + value: *const u8, +) -> IoResult<()> { + check_nonnull(name)?; + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated and nul-terminated `name`. + // * the string contents are not modified while the returned `&CStr` exists. + // * the returned `&CStr` is not held past the scope of this callback. + let name = unsafe { CStr::from_ptr(name) }; + + if value_size == 0 { + ops.erase_persistent_value(name) + } else { + check_nonnull(value)?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `value` with size `value_size`. + // * we only access the contents via the returned slice. + // * the returned slice is not held past the scope of this callback. + let value = unsafe { slice::from_raw_parts(value, value_size) }; + ops.write_persistent_value(name, value) + } +} + +/// Wraps a callback to convert the given `IoResult<>` to raw `AvbIOResult` for libavb. +/// +/// See corresponding `try_*` function docs. +unsafe extern "C" fn validate_public_key_for_partition( + ops: *mut AvbOps, + partition: *const c_char, + public_key_data: *const u8, + public_key_length: usize, + public_key_metadata: *const u8, + public_key_metadata_length: usize, + out_is_trusted: *mut bool, + out_rollback_index_location: *mut u32, +) -> AvbIOResult { + // SAFETY: see corresponding `try_*` function safety documentation. + unsafe { + result_to_io_enum(try_validate_public_key_for_partition( + ops, + partition, + public_key_data, + public_key_length, + public_key_metadata, + public_key_metadata_length, + out_is_trusted, + out_rollback_index_location, + )) + } +} + +/// Bounces the C callback into the user-provided Rust implementation. +/// +/// # Safety +/// * `ops` must have been created via `OpsBridge`. +/// * `partition` must adhere to the requirements of `CStr::from_ptr()`. +/// * `public_key_*` args must adhere to the requirements of `slice::from_raw_parts()`. +/// * `out_*` must adhere to the requirements of `ptr::write()`. +#[allow(clippy::too_many_arguments)] // Mirroring libavb C API. +unsafe fn try_validate_public_key_for_partition( + ops: *mut AvbOps, + partition: *const c_char, + public_key_data: *const u8, + public_key_length: usize, + public_key_metadata: *const u8, + public_key_metadata_length: usize, + out_is_trusted: *mut bool, + out_rollback_index_location: *mut u32, +) -> IoResult<()> { + check_nonnull(partition)?; + check_nonnull(public_key_data)?; + check_nonnull(out_is_trusted)?; + check_nonnull(out_rollback_index_location)?; + + // Initialize the output variables first in case something fails. + // SAFETY: + // * we've checked that the pointers are non-NULL. + // * libavb gives us a properly-allocated `out_*`. + unsafe { + ptr::write(out_is_trusted, false); + ptr::write(out_rollback_index_location, 0); + } + + // SAFETY: + // * we only use `ops` objects created via `OpsBridge` as required. + // * `ops` is only extracted once and is dropped at the end of the callback. + let ops = unsafe { as_ops(ops) }?; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated and nul-terminated `partition`. + // * the string contents are not modified while the returned `&CStr` exists. + // * the returned `&CStr` is not held past the scope of this callback. + let partition = unsafe { CStr::from_ptr(partition) }; + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `public_key_data` with size `public_key_length`. + // * we only access the contents via the returned slice. + // * the returned slice is not held past the scope of this callback. + let public_key = unsafe { slice::from_raw_parts(public_key_data, public_key_length) }; + let metadata = check_nonnull(public_key_metadata).ok().map( + // SAFETY: + // * we've checked that the pointer is non-NULL. + // * libavb gives us a properly-allocated `public_key_metadata` with size + // `public_key_metadata_length`. + // * we only access the contents via the returned slice. + // * the returned slice is not held past the scope of this callback. + |_| unsafe { slice::from_raw_parts(public_key_metadata, public_key_metadata_length) }, + ); + + let key_info = ops.validate_public_key_for_partition(partition, public_key, metadata)?; + + // SAFETY: + // * we've checked that the pointers are non-NULL. + // * libavb gives us a properly-allocated `out_*`. + unsafe { + ptr::write(out_is_trusted, key_info.trusted); + ptr::write( + out_rollback_index_location, + key_info.rollback_index_location, + ); + } + 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/src/verify.rs b/rust/src/verify.rs new file mode 100644 index 0000000..b718d63 --- /dev/null +++ b/rust/src/verify.rs @@ -0,0 +1,460 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Verification APIs. +//! +//! This module is responsible for all the conversions required to pass information between +//! libavb and Rust for verifying images. + +extern crate alloc; + +use crate::{ + descriptor::{get_descriptors, Descriptor, DescriptorResult}, + error::{ + slot_verify_enum_to_result, vbmeta_verify_enum_to_result, SlotVerifyError, + SlotVerifyNoDataResult, SlotVerifyResult, VbmetaVerifyResult, + }, + ops, Ops, +}; +use alloc::vec::Vec; +use avb_bindgen::{ + avb_slot_verify, avb_slot_verify_data_free, AvbPartitionData, AvbSlotVerifyData, AvbVBMetaData, +}; +use core::{ + ffi::{c_char, CStr}, + fmt, + marker::PhantomData, + pin::pin, + ptr::{self, null, null_mut, NonNull}, + slice, +}; + +/// `AvbHashtreeErrorMode`; see libavb docs for descriptions of each mode. +pub use avb_bindgen::AvbHashtreeErrorMode as HashtreeErrorMode; +/// `AvbSlotVerifyFlags`; see libavb docs for descriptions of each flag. +pub use avb_bindgen::AvbSlotVerifyFlags as SlotVerifyFlags; + +/// Returns `Err(SlotVerifyError::Internal)` if the given pointer is `NULL`. +fn check_nonnull<T>(ptr: *const T) -> SlotVerifyNoDataResult<()> { + match ptr.is_null() { + true => Err(SlotVerifyError::Internal), + false => Ok(()), + } +} + +/// Wraps a raw C `AvbVBMetaData` struct. +/// +/// This provides a Rust safe view over the raw data; no copies are made. +// +// `repr(transparent)` guarantees that size and alignment match the underlying type exactly, so that +// we can cast the array of `AvbVBMetaData` structs directly into a slice of `VbmetaData` wrappers +// without allocating any additional memory. +#[repr(transparent)] +pub struct VbmetaData(AvbVBMetaData); + +impl VbmetaData { + /// Validates the internal data so the accessors can be fail-free. This should be called on all + /// `VbmetaData` objects before they are handed to the user. + /// + /// Normally this would be done in a `new()` function but we never instantiate `VbmetaData` + /// objects ourselves, we just cast them from the C structs provided by libavb. + /// + /// Returns `Err(SlotVerifyError::Internal)` on failure. + fn validate(&self) -> SlotVerifyNoDataResult<()> { + check_nonnull(self.0.partition_name)?; + check_nonnull(self.0.vbmeta_data)?; + Ok(()) + } + + /// Returns the name of the partition this vbmeta image was loaded from. + pub fn partition_name(&self) -> &CStr { + // SAFETY: + // * libavb gives us a properly-allocated and nul-terminated string. + // * the returned contents remain valid and unmodified while we exist. + unsafe { CStr::from_ptr(self.0.partition_name) } + } + + /// Returns the vbmeta image contents. + pub fn data(&self) -> &[u8] { + // SAFETY: + // * libavb gives us a properly-allocated byte array. + // * the returned contents remain valid and unmodified while we exist. + unsafe { slice::from_raw_parts(self.0.vbmeta_data, self.0.vbmeta_size) } + } + + /// Returns the vbmeta verification result. + pub fn verify_result(&self) -> VbmetaVerifyResult<()> { + vbmeta_verify_enum_to_result(self.0.verify_result) + } + + /// Extracts the descriptors from the vbmeta image. + /// + /// Note that this function allocates memory to hold the `Descriptor` objects. + /// + /// # Returns + /// A vector of descriptors, or `DescriptorError` on failure. + pub fn descriptors(&self) -> DescriptorResult<Vec<Descriptor>> { + // SAFETY: the only way to get a `VbmetaData` object is via the return value of + // `slot_verify()`, so we know we have been properly validated. + unsafe { get_descriptors(self) } + } +} + +impl fmt::Display for VbmetaData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{:?}: {:?}", self.partition_name(), self.verify_result()) + } +} + +/// Forwards to `Display` formatting; the default `Debug` formatting implementation isn't very +/// useful as it's mostly raw pointer addresses. +impl fmt::Debug for VbmetaData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +/// Wraps a raw C `AvbPartitionData` struct. +/// +/// This provides a Rust safe view over the raw data; no copies are made. +#[repr(transparent)] +pub struct PartitionData(AvbPartitionData); + +impl PartitionData { + /// Validates the internal data so the accessors can be fail-free. This should be called on all + /// `PartitionData` objects before they are handed to the user. + /// + /// Normally this would be done in a `new()` function but we never instantiate `PartitionData` + /// objects ourselves, we just cast them from the C structs provided by libavb. + /// + /// Returns `Err(SlotVerifyError::Internal)` on failure. + fn validate(&self) -> SlotVerifyNoDataResult<()> { + check_nonnull(self.0.partition_name)?; + check_nonnull(self.0.data)?; + Ok(()) + } + + /// Returns the name of the partition this image was loaded from. + pub fn partition_name(&self) -> &CStr { + // SAFETY: + // * libavb gives us a properly-allocated and nul-terminated string. + // * the returned contents remain valid and unmodified while we exist. + unsafe { CStr::from_ptr(self.0.partition_name) } + } + + /// Returns the image contents. + pub fn data(&self) -> &[u8] { + // SAFETY: + // * libavb gives us a properly-allocated byte array. + // * the returned contents remain valid and unmodified while we exist. + unsafe { slice::from_raw_parts(self.0.data, self.0.data_size) } + } + + /// Returns whether this partition was preloaded via `get_preloaded_partition()`. + pub fn preloaded(&self) -> bool { + self.0.preloaded + } + + /// Returns the verification result for this partition. + /// + /// Only top-level `Verification` errors will contain valid `SlotVerifyData` objects, if this + /// individual partition returns a `Verification` error the error will always contain `None`. + pub fn verify_result(&self) -> SlotVerifyNoDataResult<()> { + slot_verify_enum_to_result(self.0.verify_result) + } +} + +/// A "(p)" after the partition name indicates a preloaded partition. +impl fmt::Display for PartitionData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "{:?}{}: {:?}", + self.partition_name(), + match self.preloaded() { + true => "(p)", + false => "", + }, + self.verify_result() + ) + } +} + +/// Forwards to `Display` formatting; the default `Debug` formatting implementation isn't very +/// useful as it's mostly raw pointer addresses. +impl fmt::Debug for PartitionData { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +/// Wraps a raw C `AvbSlotVerifyData` struct. +/// +/// This provides a Rust safe view over the raw data; no copies are made. +/// +/// # Lifetimes +/// * `'a`: the lifetime of any preloaded partition data borrowed from an `Ops<'a>` object. +/// +/// If the `Ops` doesn't provide any preloaded data, `SlotVerifyData` doesn't borrow anything +/// and instead allocates and owns all data internally, freeing it accordingly on `Drop`. In this +/// case, `'a` can be `'static` which imposes no lifetime restrictions on `SlotVerifyData`. +pub struct SlotVerifyData<'a> { + /// Internally owns the underlying data and deletes it on drop. + raw_data: NonNull<AvbSlotVerifyData>, + + /// This provides the necessary lifetimes so the compiler can make sure that the preloaded + /// partition data stays alive at least as long as we do, since the underlying + /// `AvbSlotVerifyData` may wrap this data rather than making a copy. + // + // We do not want to actually borrow an `Ops` here, since in some cases `Ops` is just a + // temporary object and may go out of scope before us. The only shared data is the preloaded + // partition contents, not the entire `Ops` object. + _preloaded: PhantomData<&'a [u8]>, +} + +// Useful so that `SlotVerifyError`, which may hold a `SlotVerifyData`, can derive `PartialEq`. +impl<'a> PartialEq for SlotVerifyData<'a> { + fn eq(&self, other: &Self) -> bool { + // A `SlotVerifyData` uniquely owns the underlying data so is only equal to itself. + ptr::eq(self, other) + } +} + +impl<'a> Eq for SlotVerifyData<'a> {} + +impl<'a> SlotVerifyData<'a> { + /// Creates a `SlotVerifyData` wrapping the given raw `AvbSlotVerifyData`. + /// + /// The returned `SlotVerifyData` will take ownership of the given `AvbSlotVerifyData` and + /// properly release the allocated memory when it drops. + /// + /// If `ops` provided any preloaded data, the returned `SlotVerifyData` also borrows the data to + /// account for the underlying `AvbSlotVerifyData` holding a pointer to it. If there was no + /// preloaded data, then `SlotVerifyData` owns all its data. + /// + /// # Arguments + /// * `data`: a `AvbSlotVerifyData` object created by libavb using `ops`. + /// * `ops`: the user-provided `Ops` object that was used for verification; only used here to + /// grab the preloaded data lifetime. + /// + /// # Returns + /// The new object, or `Err(SlotVerifyError::Internal)` if the data looks invalid. + /// + /// # Safety + /// * `data` must be a valid `AvbSlotVerifyData` object created by libavb using `ops`. + /// * after calling this function, do not access `data` except through the returned object + unsafe fn new( + data: *mut AvbSlotVerifyData, + _ops: &dyn Ops<'a>, + ) -> SlotVerifyNoDataResult<Self> { + let ret = Self { + raw_data: NonNull::new(data).ok_or(SlotVerifyError::Internal)?, + _preloaded: PhantomData, + }; + + // Validate all the contained data here so accessors will never fail. + // SAFETY: `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + let data = unsafe { ret.raw_data.as_ref() }; + check_nonnull(data.ab_suffix)?; + check_nonnull(data.vbmeta_images)?; + check_nonnull(data.loaded_partitions)?; + check_nonnull(data.cmdline)?; + ret.vbmeta_data().iter().try_for_each(|v| v.validate())?; + ret.partition_data().iter().try_for_each(|i| i.validate())?; + + Ok(ret) + } + + /// Returns the slot suffix string. + pub fn ab_suffix(&self) -> &CStr { + // SAFETY: + // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + // * libavb gives us a properly-allocated and nul-terminated string. + // * the returned contents remain valid and unmodified while we exist. + unsafe { CStr::from_ptr(self.raw_data.as_ref().ab_suffix) } + } + + /// Returns the `VbmetaData` structs. + pub fn vbmeta_data(&self) -> &[VbmetaData] { + // SAFETY: + // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + // * libavb gives us a properly-allocated array of structs. + // * the returned contents remain valid and unmodified while we exist. + unsafe { + slice::from_raw_parts( + // `repr(transparent)` means we can cast between these types. + self.raw_data.as_ref().vbmeta_images as *const VbmetaData, + self.raw_data.as_ref().num_vbmeta_images, + ) + } + } + + /// Returns the `PartitionData` structs. + pub fn partition_data(&self) -> &[PartitionData] { + // SAFETY: + // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + // * libavb gives us a properly-allocated array of structs. + // * the returned contents remain valid and unmodified while we exist. + unsafe { + slice::from_raw_parts( + // `repr(transparent)` means we can cast between these types. + self.raw_data.as_ref().loaded_partitions as *const PartitionData, + self.raw_data.as_ref().num_loaded_partitions, + ) + } + } + + /// Returns the kernel commandline. + pub fn cmdline(&self) -> &CStr { + // SAFETY: + // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + // * libavb gives us a properly-allocated and nul-terminated string. + // * the returned contents remain valid and unmodified while we exist. + unsafe { CStr::from_ptr(self.raw_data.as_ref().cmdline) } + } + + /// Returns the rollback indices. + pub fn rollback_indexes(&self) -> &[u64] { + // SAFETY: `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + &unsafe { self.raw_data.as_ref() }.rollback_indexes[..] + } + + /// Returns the resolved hashtree error mode. + pub fn resolved_hashtree_error_mode(&self) -> HashtreeErrorMode { + // SAFETY: `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + unsafe { self.raw_data.as_ref() }.resolved_hashtree_error_mode + } +} + +/// Frees any internally-allocated and owned data. +impl<'a> Drop for SlotVerifyData<'a> { + fn drop(&mut self) { + // SAFETY: + // * `raw_data` points to a valid `AvbSlotVerifyData` object owned by us. + // * libavb created the object and requires us to free it by calling this function. + unsafe { avb_slot_verify_data_free(self.raw_data.as_ptr()) }; + } +} + +/// Implements `Display` to make it easy to print some basic information. +/// +/// This implementation will print the slot, partition name, and verification status for all +/// vbmetadata and images. +impl<'a> fmt::Display for SlotVerifyData<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!( + f, + "slot: {:?}, vbmeta: {:?}, images: {:?}", + self.ab_suffix(), + self.vbmeta_data(), + self.partition_data() + ) + } +} + +/// Forwards to `Display` formatting; the default `Debug` formatting implementation isn't very +/// useful as it's mostly raw pointer addresses. +impl<'a> fmt::Debug for SlotVerifyData<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt::Display::fmt(self, f) + } +} + +/// Performs verification of the requested images. +/// +/// This wraps `avb_slot_verify()` for Rust, see the original docs for more details. +/// +/// # Arguments +/// * `ops`: implementation of the required verification callbacks. +/// * `requested_partition`: the set of partition names to verify. +/// * `ab_suffix`: the slot suffix to append to the partition names, or None. +/// * `flags`: flags to configure verification. +/// * `hashtree_error_mode`: desired error handling behavior. +/// +/// # Returns +/// `Ok` if verification completed successfully, the verification error otherwise. `SlotVerifyData` +/// will be returned in two cases: +/// +/// 1. always returned on verification success +/// 2. if `AllowVerificationError` is given in `flags`, it will also be returned on verification +/// failure +/// +/// A returned `SlotVerifyData` will also borrow any preloaded data provided by `ops`. The `ops` +/// object itself can go out of scope, but any preloaded data it could provide must outlive the +/// returned object. +pub fn slot_verify<'a>( + ops: &mut dyn Ops<'a>, + requested_partitions: &[&CStr], + ab_suffix: Option<&CStr>, + flags: SlotVerifyFlags, + hashtree_error_mode: HashtreeErrorMode, +) -> SlotVerifyResult<'a, SlotVerifyData<'a>> { + // libavb detects the size of the `requested_partitions` array by NULL termination. Expecting + // the Rust caller to do this would make the API much more awkward, so we populate a + // NULL-terminated array of c-string pointers ourselves. For now we use a fixed-sized array + // rather than dynamically allocating, 8 should be more than enough. + const MAX_PARTITION_ARRAY_SIZE: usize = 8 + 1; // Max 8 partition names + 1 for NULL terminator. + if requested_partitions.len() >= MAX_PARTITION_ARRAY_SIZE { + return Err(SlotVerifyError::Internal); + } + let mut partitions_array = [null() as *const c_char; MAX_PARTITION_ARRAY_SIZE]; + for (source, dest) in requested_partitions.iter().zip(partitions_array.iter_mut()) { + *dest = source.as_ptr(); + } + + // To be more Rust idiomatic we allow `ab_suffix` to be `None`, but libavb requires a valid + // pointer to an empty string in this case, not NULL. + let ab_suffix = ab_suffix.unwrap_or(CStr::from_bytes_with_nul(b"\0").unwrap()); + + let ops_bridge = pin!(ops::OpsBridge::new(ops)); + let mut out_data: *mut AvbSlotVerifyData = null_mut(); + + // Call the libavb verification function. + // + // Note: do not use the `?` operator to return-early here; in some cases `out_data` will be + // allocated and returned even on verification failure, and we need to take ownership of it + // or else the memory will leak. + // + // SAFETY: + // * `ops_bridge.init_and_get_c_ops()` gives us a valid `AvbOps`. + // * we've properly initialized all objects passed into libavb. + // * if `out_data` is non-null on return, we take ownership via `SlotVerifyData`. + let result = slot_verify_enum_to_result(unsafe { + avb_slot_verify( + ops_bridge.init_and_get_c_ops(), + partitions_array.as_ptr(), + ab_suffix.as_ptr(), + flags, + hashtree_error_mode, + &mut out_data, + ) + }); + + // If `out_data` is non-null, take ownership so memory gets released on drop. + let data = match out_data.is_null() { + true => None, + // SAFETY: `out_data` was properly allocated by libavb and ownership has passed to us. + false => Some(unsafe { SlotVerifyData::new(out_data, ops)? }), + }; + + // Fold the verify data into the result. + match result { + // libavb will always provide verification data on success. + Ok(()) => Ok(data.unwrap()), + // Data may also be provided on verification failure, fold it into the error. + Err(SlotVerifyError::Verification(None)) => Err(SlotVerifyError::Verification(data)), + // No other error provides verification data. + Err(e) => Err(e), + } +} diff --git a/rust/testdata/chain_partition_descriptor.bin b/rust/testdata/chain_partition_descriptor.bin Binary files differnew file mode 100644 index 0000000..5331273 --- /dev/null +++ b/rust/testdata/chain_partition_descriptor.bin diff --git a/rust/testdata/hash_descriptor.bin b/rust/testdata/hash_descriptor.bin Binary files differnew file mode 100644 index 0000000..940732e --- /dev/null +++ b/rust/testdata/hash_descriptor.bin diff --git a/rust/testdata/hashtree_descriptor.bin b/rust/testdata/hashtree_descriptor.bin Binary files differnew file mode 100644 index 0000000..cf2b264 --- /dev/null +++ b/rust/testdata/hashtree_descriptor.bin diff --git a/rust/testdata/kernel_commandline_descriptor.bin b/rust/testdata/kernel_commandline_descriptor.bin Binary files differnew file mode 100644 index 0000000..f6f3fde --- /dev/null +++ b/rust/testdata/kernel_commandline_descriptor.bin diff --git a/rust/testdata/property_descriptor.bin b/rust/testdata/property_descriptor.bin Binary files differnew file mode 100644 index 0000000..ea78832 --- /dev/null +++ b/rust/testdata/property_descriptor.bin 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 new file mode 100644 index 0000000..2c25c74 --- /dev/null +++ b/rust/tests/test_ops.rs @@ -0,0 +1,416 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Provides `avb::Ops` test fixtures. + +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; + +/// Where the fake partition contents come from. +pub enum PartitionContents<'a> { + /// Read on-demand from disk. + FromDisk(Vec<u8>), + /// Preloaded and passed in. + Preloaded(&'a [u8]), +} + +impl<'a> PartitionContents<'a> { + /// Returns the partition data. + pub fn as_slice(&self) -> &[u8] { + match self { + Self::FromDisk(v) => v, + Self::Preloaded(c) => c, + } + } + + /// Returns a mutable reference to the `FromDisk` data for test modification. Panicks if the + /// data is actually `Preloaded` instead. + pub fn as_mut_vec(&mut self) -> &mut Vec<u8> { + match self { + Self::FromDisk(v) => v, + Self::Preloaded(_) => panic!("Cannot mutate preloaded partition data"), + } + } +} + +/// Represents a single fake partition. +pub struct FakePartition<'a> { + /// Partition contents, either preloaded or read on-demand. + pub contents: PartitionContents<'a>, + + /// Partition UUID. + #[cfg(feature = "uuid")] + pub uuid: Uuid, +} + +impl<'a> FakePartition<'a> { + fn new(contents: PartitionContents<'a>) -> Self { + Self { + contents, + #[cfg(feature = "uuid")] + uuid: Default::default(), + } + } +} + +/// 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. +/// +/// The user is expected to set up the internal values to the desired device state - disk contents, +/// rollback indices, etc. This class then uses this state to implement the avb callback operations. +pub struct TestOps<'a> { + /// Partitions to provide to libavb callbacks. + pub partitions: HashMap<&'static str, FakePartition<'a>>, + + /// 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. + /// + /// 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>, + + /// Unlock state. Set an error to simulate IoError during access. + pub unlock_state: IoResult<bool>, + + /// Persistent named values. Set an error to simulate `IoError` during access. Writing + /// 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> { + /// Adds a fake on-disk partition with the given contents. + /// + /// Reduces boilerplate a bit by taking in a raw array and returning a &mut so tests can + /// do something like this: + /// + /// ``` + /// test_ops.add_partition("foo", [1, 2, 3, 4]); + /// test_ops.add_partition("bar", [0, 0]).uuid = uuid!(...); + /// ``` + pub fn add_partition<T: Into<Vec<u8>>>( + &mut self, + name: &'static str, + contents: T, + ) -> &mut FakePartition<'a> { + self.partitions.insert( + name, + FakePartition::new(PartitionContents::FromDisk(contents.into())), + ); + self.partitions.get_mut(name).unwrap() + } + + /// Adds a preloaded partition with the given contents. + /// + /// Same a `add_partition()` except that the preloaded data is not owned by + /// the `TestOps` but passed in, which means it can outlive `TestOps`. + pub fn add_preloaded_partition( + &mut self, + name: &'static str, + contents: &'a [u8], + ) -> &mut FakePartition<'a> { + self.partitions.insert( + name, + FakePartition::new(PartitionContents::Preloaded(contents)), + ); + self.partitions.get_mut(name).unwrap() + } + + /// Adds a persistent value with the given state. + /// + /// Reduces boilerplate by allowing array input: + /// + /// ``` + /// test_ops.add_persistent_value("foo", Ok(b"contents")); + /// test_ops.add_persistent_value("bar", Err(IoError::NoSuchValue)); + /// ``` + pub fn add_persistent_value(&mut self, name: &str, contents: IoResult<&[u8]>) { + self.persistent_values + .insert(name.into(), contents.map(|b| b.into())); + } + + /// Internal helper to validate a vbmeta key. + fn validate_fake_key( + &mut self, + 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) + } + } + } +} + +impl Default for TestOps<'_> { + fn default() -> Self { + Self { + partitions: 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(), + } + } +} + +impl<'a> Ops<'a> for TestOps<'a> { + fn read_from_partition( + &mut self, + partition: &CStr, + offset: i64, + buffer: &mut [u8], + ) -> IoResult<usize> { + let contents = self + .partitions + .get(partition.to_str()?) + .ok_or(IoError::NoSuchPartition)? + .contents + .as_slice(); + + // Negative offset means count backwards from the end. + let offset = { + if offset < 0 { + offset + .checked_add(i64::try_from(contents.len()).unwrap()) + .unwrap() + } else { + offset + } + }; + if offset < 0 { + return Err(IoError::RangeOutsidePartition); + } + let offset = usize::try_from(offset).unwrap(); + + if offset >= contents.len() { + return Err(IoError::RangeOutsidePartition); + } + + // Truncating is allowed for reads past the partition end. + let end = min(offset.checked_add(buffer.len()).unwrap(), contents.len()); + let bytes_read = end - offset; + + buffer[..bytes_read].copy_from_slice(&contents[offset..end]); + Ok(bytes_read) + } + + fn get_preloaded_partition(&mut self, partition: &CStr) -> IoResult<&'a [u8]> { + match self.partitions.get(partition.to_str()?) { + Some(FakePartition { + contents: PartitionContents::Preloaded(preloaded), + .. + }) => Ok(&preloaded[..]), + _ => Err(IoError::NotImplemented), + } + } + + fn validate_vbmeta_public_key( + &mut self, + public_key: &[u8], + public_key_metadata: Option<&[u8]>, + ) -> IoResult<bool> { + self.validate_fake_key(None, public_key, public_key_metadata) + } + + fn read_rollback_index(&mut self, location: usize) -> IoResult<u64> { + self.rollbacks.get(&location).ok_or(IoError::Io).copied() + } + + fn write_rollback_index(&mut self, location: usize, index: u64) -> IoResult<()> { + *(self.rollbacks.get_mut(&location).ok_or(IoError::Io)?) = index; + Ok(()) + } + + fn read_is_device_unlocked(&mut self) -> IoResult<bool> { + self.unlock_state.clone() + } + + #[cfg(feature = "uuid")] + fn get_unique_guid_for_partition(&mut self, partition: &CStr) -> IoResult<Uuid> { + self.partitions + .get(partition.to_str()?) + .map(|p| p.uuid) + .ok_or(IoError::NoSuchPartition) + } + + fn get_size_of_partition(&mut self, partition: &CStr) -> IoResult<u64> { + self.partitions + .get(partition.to_str()?) + .map(|p| u64::try_from(p.contents.as_slice().len()).unwrap()) + .ok_or(IoError::NoSuchPartition) + } + + fn read_persistent_value(&mut self, name: &CStr, value: &mut [u8]) -> IoResult<usize> { + match self + .persistent_values + .get(name.to_str()?) + .ok_or(IoError::NoSuchValue)? + { + // If we were given enough space, write the value contents. + Ok(contents) if contents.len() <= value.len() => { + value[..contents.len()].clone_from_slice(contents); + Ok(contents.len()) + } + // Not enough space, tell the caller how much we need. + Ok(contents) => Err(IoError::InsufficientSpace(contents.len())), + // Simulated error, return it. + Err(e) => Err(e.clone()), + } + } + + fn write_persistent_value(&mut self, name: &CStr, value: &[u8]) -> IoResult<()> { + let name = name.to_str()?; + + // If the test requested a simulated error on this value, return it. + if let Some(Err(e)) = self.persistent_values.get(name) { + return Err(e.clone()); + } + + self.persistent_values + .insert(name.to_string(), Ok(value.to_vec())); + Ok(()) + } + + fn erase_persistent_value(&mut self, name: &CStr) -> IoResult<()> { + let name = name.to_str()?; + + // If the test requested a simulated error on this value, return it. + if let Some(Err(e)) = self.persistent_values.get(name) { + return Err(e.clone()); + } + + self.persistent_values.remove(name); + Ok(()) + } + + fn validate_public_key_for_partition( + &mut self, + partition: &CStr, + public_key: &[u8], + public_key_metadata: Option<&[u8]>, + ) -> IoResult<PublicKeyForPartitionInfo> { + 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, + } + } +} + +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 new file mode 100644 index 0000000..d747ac3 --- /dev/null +++ b/rust/tests/tests.rs @@ -0,0 +1,53 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +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 new file mode 100644 index 0000000..dfe4ec8 --- /dev/null +++ b/rust/tests/verify_tests.rs @@ -0,0 +1,796 @@ +// Copyright 2023, The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! libavb_rs verification tests. + +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, + HashtreeErrorMode, IoError, KernelCommandlineDescriptor, KernelCommandlineDescriptorFlags, + PropertyDescriptor, SlotVerifyData, SlotVerifyError, SlotVerifyFlags, SlotVerifyResult, +}; +use hex::decode; +use std::{ffi::CString, fs}; +#[cfg(feature = "uuid")] +use uuid::uuid; + +/// Initializes a `TestOps` object such that verification will succeed on `TEST_PARTITION_NAME` and +/// `TEST_PARTITION_2_NAME`. +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()); + ops.add_partition("vbmeta", fs::read(TEST_VBMETA_2_PARTITIONS_PATH).unwrap()); + ops +} + +/// Calls `slot_verify()` for both test partitions. +fn verify_two_images<'a>(ops: &mut TestOps<'a>) -> SlotVerifyResult<'a, SlotVerifyData<'a>> { + slot_verify( + ops, + &[ + &CString::new(TEST_PARTITION_NAME).unwrap(), + &CString::new(TEST_PARTITION_2_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 the `boot` partition with +/// a combined image + 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", + fs::read(TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_BOOT_PATH).unwrap(), + ); + ops +} + +/// 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, + &[&CString::new("boot").unwrap()], + None, + // libavb has some special-case handling to automatically detect a combined image + vbmeta + // in the `boot` partition; don't pass the `AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION` flag + // so we can test this behavior. + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ) +} + +/// Initializes a `TestOps` object such that verification will succeed on +/// `TEST_PARTITION_PERSISTENT_DIGEST_NAME`. +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( + "vbmeta", + fs::read(TEST_VBMETA_PERSISTENT_DIGEST_PATH).unwrap(), + ); + // Register the image contents to be stored via persistent digest. + ops.add_partition(TEST_PARTITION_PERSISTENT_DIGEST_NAME, image); + ops +} + +/// 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, + &[&CString::new(TEST_PARTITION_PERSISTENT_DIGEST_NAME).unwrap()], + None, + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ) +} + +/// Modifies the partition contents by flipping a bit. +fn modify_partition_contents(ops: &mut TestOps, partition: &str) { + ops.partitions + .get_mut(partition) + .unwrap() + .contents + .as_mut_vec()[0] ^= 0x01; +} + +/// Returns the persistent value name for `TEST_PARTITION_PERSISTENT_DIGEST_NAME`. +fn persistent_digest_value_name() -> String { + // This exact format is a libavb implementation detail but is unlikely to change. If it does + // just update this format to match. + format!("avb.persistent_digest.{TEST_PARTITION_PERSISTENT_DIGEST_NAME}") +} + +#[test] +fn one_image_one_vbmeta_passes_verification_with_correct_data() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + // Make sure the resulting `SlotVerifyData` looks correct. + let data = result.unwrap(); + assert_eq!(data.ab_suffix().to_bytes(), b""); + // We don't care about the exact commandline, just search for a substring we know will + // exist to make sure the commandline is being provided to the caller correctly. + assert!(data + .cmdline() + .to_str() + .unwrap() + .contains("androidboot.vbmeta.device_state=locked")); + assert_eq!(data.rollback_indexes(), &[0; 32]); + assert_eq!( + data.resolved_hashtree_error_mode(), + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO + ); + + // Check the `VbmetaData` struct looks correct. + assert_eq!(data.vbmeta_data().len(), 1); + let vbmeta_data = &data.vbmeta_data()[0]; + assert_eq!(vbmeta_data.partition_name().to_str().unwrap(), "vbmeta"); + assert_eq!(vbmeta_data.data(), fs::read(TEST_VBMETA_PATH).unwrap()); + assert_eq!(vbmeta_data.verify_result(), Ok(())); + + // Check the `PartitionData` struct looks correct. + assert_eq!(data.partition_data().len(), 1); + let partition_data = &data.partition_data()[0]; + assert_eq!( + partition_data.partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + assert_eq!(partition_data.data(), fs::read(TEST_IMAGE_PATH).unwrap()); + assert!(!partition_data.preloaded()); + assert!(partition_data.verify_result().is_ok()); +} + +#[test] +fn preloaded_image_passes_verification() { + 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); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + let partition_data = &data.partition_data()[0]; + assert!(partition_data.preloaded()); +} + +// When all images are loaded from disk (rather than preloaded), libavb allocates memory itself for +// the data, so there is no shared ownership; the returned verification data owns the image data +// and can hold onto it even after the `ops` goes away. +#[test] +fn verification_data_from_disk_can_outlive_ops() { + let result = { + let mut ops = build_test_ops_one_image_one_vbmeta(); + verify_one_image_one_vbmeta(&mut ops) + }; + + let data = result.unwrap(); + + // The verification data owns the images and we can still access them. + assert_eq!( + data.partition_data()[0].data(), + fs::read(TEST_IMAGE_PATH).unwrap() + ); +} + +// When preloaded data is passed into ops but outlives it, we can also continue to access it from +// the verification data after the ops goes away. The ops was only borrowing it, and now the +// verification data continues to borrow it. +#[test] +fn verification_data_preloaded_can_outlive_ops() { + let preloaded = fs::read(TEST_IMAGE_PATH).unwrap(); + + let result = { + 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) + }; + + let data = result.unwrap(); + + // The verification data is borrowing the preloaded images and we can still access them. + assert_eq!(data.partition_data()[0].data(), preloaded); +} + +// When preloaded data is passed into ops but also goes out of scope, the verification data loses +// access to it, violating lifetime rules. +// +// Our lifetimes *must* be configured such that this does not compile, since `result` is borrowing +// `preloaded` which has gone out of scope. +// +// TODO: figure out how to make a compile-fail test; for now we just have to manually test by +// un-commenting the code. +// #[test] +// fn verification_data_preloaded_cannot_outlive_result() { +// let result = { +// let preloaded = fs::read(TEST_IMAGE_PATH).unwrap(); +// 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) +// }; +// result.unwrap(); +// } + +#[test] +fn slotted_partition_passes_verification() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + // Move the partitions to a "_c" slot. + ops.partitions.clear(); + ops.add_partition( + TEST_PARTITION_SLOT_C_NAME, + fs::read(TEST_IMAGE_PATH).unwrap(), + ); + ops.add_partition("vbmeta_c", fs::read(TEST_VBMETA_PATH).unwrap()); + + let result = slot_verify( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + Some(&CString::new("_c").unwrap()), + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NONE, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + let data = result.unwrap(); + assert_eq!(data.ab_suffix().to_bytes(), b"_c"); +} + +#[test] +fn two_images_one_vbmeta_passes_verification() { + let mut ops = build_test_ops_two_images_one_vbmeta(); + + let result = verify_two_images(&mut ops); + + // We should still only have 1 `VbmetaData` since we only used 1 vbmeta image, but it + // signed 2 partitions so we should have 2 `PartitionData` objects. + let data = result.unwrap(); + assert_eq!(data.vbmeta_data().len(), 1); + assert_eq!(data.partition_data().len(), 2); + assert_eq!( + data.partition_data()[0].partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + assert_eq!( + data.partition_data()[1].partition_name().to_str().unwrap(), + TEST_PARTITION_2_NAME + ); +} + +#[test] +fn combined_image_vbmeta_partition_passes_verification() { + 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 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, + ( + 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( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + None, + // Tell libavb that the vbmeta image is embedded, not in its own partition. + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_NO_VBMETA_PARTITION, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + let data = result.unwrap(); + + // Vbmeta should indicate that it came from `TEST_PARTITION_NAME`. + assert_eq!(data.vbmeta_data().len(), 1); + let vbmeta_data = &data.vbmeta_data()[0]; + assert_eq!( + vbmeta_data.partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + + // Partition should indicate that it came from `TEST_PARTITION_NAME`, but only contain the + // image contents. + assert_eq!(data.partition_data().len(), 1); + let partition_data = &data.partition_data()[0]; + assert_eq!( + partition_data.partition_name().to_str().unwrap(), + TEST_PARTITION_NAME + ); + assert_eq!(partition_data.data(), fs::read(TEST_IMAGE_PATH).unwrap()); +} + +// 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 = build_test_ops_boot_partition(); + + let result = verify_boot_partition(&mut ops); + + let data = result.unwrap(); + + // Vbmeta should indicate that it came from `boot`. + assert_eq!(data.vbmeta_data().len(), 1); + let vbmeta_data = &data.vbmeta_data()[0]; + assert_eq!(vbmeta_data.partition_name().to_str().unwrap(), "boot"); + + // Partition should indicate that it came from `boot`, but only contain the image contents. + assert_eq!(data.partition_data().len(), 1); + let partition_data = &data.partition_data()[0]; + assert_eq!(partition_data.partition_name().to_str().unwrap(), "boot"); + assert_eq!(partition_data.data(), fs::read(TEST_IMAGE_PATH).unwrap()); +} + +#[test] +fn persistent_digest_verification_updates_persistent_value() { + // With persistent digests, the image hash isn't stored in the descriptor, but is instead + // 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 = build_test_ops_persistent_digest(image_contents.clone()); + + { + let result = verify_persistent_digest(&mut ops); + let data = result.unwrap(); + assert_eq!(data.partition_data()[0].data(), image_contents); + } // Drop `result` here so it releases `ops` and we can use it again. + + assert!(ops + .persistent_values + .contains_key(&persistent_digest_value_name())); +} + +#[cfg(feature = "uuid")] +#[test] +fn successful_verification_substitutes_partition_guid() { + 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); + + let data = result.unwrap(); + assert!(data + .cmdline() + .to_str() + .unwrap() + .contains("androidboot.vbmeta.device=PARTUUID=01234567-89ab-cdef-0123-456789abcdef")); +} + +#[cfg(feature = "uuid")] +#[test] +fn successful_verification_substitutes_boot_partition_guid() { + 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); + + let data = result.unwrap(); + // In this case libavb substitutes the `boot` partition GUID in for `vbmeta`. + assert!(data + .cmdline() + .to_str() + .unwrap() + .contains("androidboot.vbmeta.device=PARTUUID=01234567-89ab-cdef-0123-456789abcdef")); +} + +#[test] +fn corrupted_image_fails_verification() { + 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); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Verification(None))); +} + +#[test] +fn read_partition_callback_error_fails_verification() { + 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); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn undersized_partition_fails_verification() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + ops.partitions + .get_mut(TEST_PARTITION_NAME) + .unwrap() + .contents + .as_mut_vec() + .pop(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn corrupted_vbmeta_fails_verification() { + 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); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::InvalidMetadata)); +} + +#[test] +fn rollback_violation_fails_verification() { + 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); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::RollbackIndex)); +} + +#[test] +fn rollback_callback_error_fails_verification() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + ops.rollbacks.clear(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn untrusted_vbmeta_keys_fails_verification() { + 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); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::PublicKeyRejected)); +} + +#[test] +fn vbmeta_keys_callback_error_fails_verification() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + ops.default_vbmeta_key = None; + + let result = verify_one_image_one_vbmeta(&mut ops); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn unlock_state_callback_error_fails_verification() { + 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); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn persistent_digest_mismatch_fails_verification() { + let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE]; + 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. + let original_persistent_values = ops.persistent_values.clone(); + + assert!(verify_persistent_digest(&mut ops).is_err()); + + // Persistent value should be unchanged. + assert_eq!(ops.persistent_values, original_persistent_values); +} + +#[test] +fn persistent_digest_callback_error_fails_verification() { + let image_contents = vec![0xAAu8; TEST_IMAGE_SIZE]; + 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); + + let error = result.unwrap_err(); + assert!(matches!(error, SlotVerifyError::Io)); +} + +#[test] +fn corrupted_image_with_allow_verification_error_flag_fails_verification_with_data() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + modify_partition_contents(&mut ops, TEST_PARTITION_NAME); + + let result = slot_verify( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + None, + // Pass the flag to allow verification errors. + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + // Verification should fail, but with the `AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR` flag + // it should give us back the verification data. + let error = result.unwrap_err(); + let data = match error { + SlotVerifyError::Verification(Some(data)) => data, + _ => panic!("Expected verification data to exist"), + }; + + // vbmeta verification should have succeeded since that image was still correct. + assert_eq!(data.vbmeta_data().len(), 1); + assert_eq!(data.vbmeta_data()[0].verify_result(), Ok(())); + // Partition verification should have failed since we modified the image. + assert_eq!(data.partition_data().len(), 1); + assert!(matches!( + data.partition_data()[0].verify_result(), + Err(SlotVerifyError::Verification(None)) + )); +} + +#[test] +fn one_image_one_vbmeta_verification_data_display() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part": Ok(())]"# + ); +} + +#[test] +fn preloaded_image_verification_data_display() { + 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); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part"(p): Ok(())]"# + ); +} + +#[test] +fn two_images_one_vbmeta_verification_data_display() { + let mut ops = build_test_ops_two_images_one_vbmeta(); + + let result = verify_two_images(&mut ops); + + let data = result.unwrap(); + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part": Ok(()), "test_part_2": Ok(())]"# + ); +} + +#[test] +fn corrupted_image_verification_data_display() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + modify_partition_contents(&mut ops, TEST_PARTITION_NAME); + + let result = slot_verify( + &mut ops, + &[&CString::new(TEST_PARTITION_NAME).unwrap()], + None, + SlotVerifyFlags::AVB_SLOT_VERIFY_FLAGS_ALLOW_VERIFICATION_ERROR, + HashtreeErrorMode::AVB_HASHTREE_ERROR_MODE_EIO, + ); + + let error = result.unwrap_err(); + let data = match error { + SlotVerifyError::Verification(Some(data)) => data, + _ => panic!("Expected verification data to exist"), + }; + assert_eq!( + format!("{data}"), + r#"slot: "", vbmeta: ["vbmeta": Ok(())], images: ["test_part": Err(Verification(None))]"# + ); +} + +#[test] +fn one_image_gives_single_descriptor() { + let mut ops = build_test_ops_one_image_one_vbmeta(); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + assert_eq!(data.vbmeta_data()[0].descriptors().unwrap().len(), 1); +} + +#[test] +fn two_images_gives_two_descriptors() { + let mut ops = build_test_ops_two_images_one_vbmeta(); + + let result = verify_two_images(&mut ops); + + let data = result.unwrap(); + assert_eq!(data.vbmeta_data()[0].descriptors().unwrap().len(), 2); +} + +/// Runs verification on the given contents and checks for a resulting descriptor. +/// +/// This test helper performs the following steps: +/// +/// 1. set up a `TestOps` for the default test image/vbmeta +/// 2. replace the vbmeta image with the contents at `vbmeta_path` +/// 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 = build_test_ops_one_image_one_vbmeta(); + + // Replace the vbmeta image with the requested variation. + ops.add_partition("vbmeta", fs::read(vbmeta_path).unwrap()); + + let result = verify_one_image_one_vbmeta(&mut ops); + + let data = result.unwrap(); + let descriptors = &data.vbmeta_data()[0].descriptors().unwrap(); + assert!(descriptors.contains(expected_descriptor)); +} + +#[test] +fn verify_hash_descriptor() { + verify_and_find_descriptor( + // The standard vbmeta image should contain the hash descriptor. + TEST_VBMETA_PATH, + &Descriptor::Hash(HashDescriptor { + image_size: TEST_IMAGE_SIZE as u64, + hash_algorithm: TEST_IMAGE_HASH_ALGO, + flags: HashDescriptorFlags(0), + partition_name: TEST_PARTITION_NAME, + salt: &decode(TEST_IMAGE_SALT_HEX).unwrap(), + digest: &decode(TEST_IMAGE_DIGEST_HEX).unwrap(), + }), + ); +} + +#[test] +fn verify_property_descriptor() { + verify_and_find_descriptor( + TEST_VBMETA_WITH_PROPERTY_PATH, + &Descriptor::Property(PropertyDescriptor { + key: TEST_PROPERTY_KEY, + value: TEST_PROPERTY_VALUE, + }), + ); +} + +#[test] +fn verify_hashtree_descriptor() { + verify_and_find_descriptor( + TEST_VBMETA_WITH_HASHTREE_PATH, + &Descriptor::Hashtree(HashtreeDescriptor { + dm_verity_version: 1, + image_size: TEST_IMAGE_SIZE as u64, + tree_offset: TEST_IMAGE_SIZE as u64, + tree_size: 4096, + data_block_size: 4096, + hash_block_size: 4096, + fec_num_roots: 0, + fec_offset: 0, + fec_size: 0, + hash_algorithm: TEST_HASHTREE_ALGORITHM, + flags: HashtreeDescriptorFlags(0), + partition_name: TEST_PARTITION_HASH_TREE_NAME, + salt: &decode(TEST_HASHTREE_SALT_HEX).unwrap(), + root_digest: &decode(TEST_HASHTREE_DIGEST_HEX).unwrap(), + }), + ); +} + +#[test] +fn verify_kernel_commandline_descriptor() { + verify_and_find_descriptor( + TEST_VBMETA_WITH_COMMANDLINE_PATH, + &Descriptor::KernelCommandline(KernelCommandlineDescriptor { + flags: KernelCommandlineDescriptorFlags(0), + commandline: TEST_KERNEL_COMMANDLINE, + }), + ); +} + +#[test] +fn verify_chain_partition_descriptor() { + 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 + // * a signed test image with vbmeta footer in TEST_PARTITION_2_NAME + // * a vbmeta image in "vbmeta" which: + // * signs the default TEST_PARTITION_NAME image + // * chains to TEST_PARTITION_2_NAME + // + // Since this is an unusual configuration, it's simpler to just set it up manually here + // rather than try to adapt `verify_and_find_descriptor()` for this one case. + ops.add_partition( + "vbmeta", + fs::read(TEST_VBMETA_WITH_CHAINED_PARTITION_PATH).unwrap(), + ); + // Replace the chained partition with the combined image + vbmeta footer. + ops.add_partition( + TEST_PARTITION_2_NAME, + fs::read(TEST_IMAGE_WITH_VBMETA_FOOTER_FOR_TEST_PART_2).unwrap(), + ); + // Add the rollback index for the chained partition's location. + ops.rollbacks.insert( + TEST_CHAINED_PARTITION_ROLLBACK_LOCATION, + TEST_CHAINED_PARTITION_ROLLBACK_INDEX, + ); + + let result = verify_two_images(&mut ops); + + let data = result.unwrap(); + // We should have two vbmeta images - one from the "vbmeta" partition, the other embedded + // in the footer of TEST_PARTITION_2_NAME. + let vbmetas = data.vbmeta_data(); + assert_eq!(vbmetas.len(), 2); + // Search for the main vbmeta so we don't assume any particular order. + let main_vbmeta = vbmetas + .iter() + .find(|v| v.partition_name().to_str().unwrap() == "vbmeta") + .unwrap(); + + // The main vbmeta should contain the chain descriptor. + let expected = ChainPartitionDescriptor { + rollback_index_location: TEST_CHAINED_PARTITION_ROLLBACK_LOCATION as u32, + partition_name: TEST_PARTITION_2_NAME, + public_key: &fs::read(TEST_PUBLIC_KEY_RSA8192_PATH).unwrap(), + flags: ChainPartitionDescriptorFlags(0), + }; + assert!(main_vbmeta + .descriptors() + .unwrap() + .contains(&Descriptor::ChainPartition(expected))); +} diff --git a/test/Android.bp b/test/Android.bp index 8d8809a..b5e7dd5 100644 --- a/test/Android.bp +++ b/test/Android.bp @@ -34,11 +34,11 @@ python_test_host { "at_auth_unlock", ], data: [ - "data/atx_pik_certificate.bin", - "data/atx_puk_certificate.bin", - "data/atx_unlock_challenge.bin", - "data/atx_unlock_credential.bin", - "data/testkey_atx_puk.pem", + "data/cert_pik_certificate.bin", + "data/cert_puk_certificate.bin", + "data/cert_unlock_challenge.bin", + "data/cert_unlock_credential.bin", + "data/testkey_cert_puk.pem", ], test_config: "at_auth_unlock_unittest.xml", } @@ -58,6 +58,38 @@ filegroup { srcs: ["data/testkey_rsa2048.pem"], } +// libavb_cert test public key. +filegroup { + name: "avb_cert_testkey_psk", + srcs: ["data/testkey_cert_psk.pem"], +} + +// libavb_cert test public key metadata. PSK and PIK versions are both set to 42. +filegroup { + name: "avb_cert_test_metadata", + srcs: ["data/cert_metadata.bin"], +} + +// libavb_cert test permanent attributes for the above public key. +filegroup { + name: "avb_cert_test_permanent_attributes", + srcs: ["data/cert_permanent_attributes.bin"], +} + +// libavb_cert test RNG for an unlock challenge. +// Note: this is only the 16-byte randomization, not a full +// `AvbCertUnlockChallenge`. +filegroup { + name: "avb_cert_test_unlock_challenge", + srcs: ["data/cert_unlock_challenge.bin"], +} + +// libavb_cert test unlock credential signing `avb_cert_test_unlock_challenge`. +filegroup { + name: "avb_cert_test_unlock_credential", + srcs: ["data/cert_unlock_credential.bin"], +} + genrule { name: "avb_testkey_rsa2048_pub_bin", tools: ["avbtool"], diff --git a/test/at_auth_unlock_unittest.py b/test/at_auth_unlock_unittest.py index 7683565..adc045b 100644 --- a/test/at_auth_unlock_unittest.py +++ b/test/at_auth_unlock_unittest.py @@ -32,11 +32,11 @@ def dataPath(file): return os.path.join(os.path.dirname(__file__), 'data', file) -DATA_FILE_PIK_CERTIFICATE = dataPath('atx_pik_certificate.bin') -DATA_FILE_PUK_CERTIFICATE = dataPath('atx_puk_certificate.bin') -DATA_FILE_PUK_KEY = dataPath('testkey_atx_puk.pem') -DATA_FILE_UNLOCK_CHALLENGE = dataPath('atx_unlock_challenge.bin') -DATA_FILE_UNLOCK_CREDENTIAL = dataPath('atx_unlock_credential.bin') +DATA_FILE_PIK_CERTIFICATE = dataPath('cert_pik_certificate.bin') +DATA_FILE_PUK_CERTIFICATE = dataPath('cert_puk_certificate.bin') +DATA_FILE_PUK_KEY = dataPath('testkey_cert_puk.pem') +DATA_FILE_UNLOCK_CHALLENGE = dataPath('cert_unlock_challenge.bin') +DATA_FILE_UNLOCK_CREDENTIAL = dataPath('cert_unlock_credential.bin') def createTempZip(contents): @@ -197,7 +197,7 @@ class UnlockCredentialsTest(unittest.TestCase): def writeFullUnlockChallenge(out_file, product_id_hash=None): - """Helper function to create a file with a full AvbAtxUnlockChallenge struct. + """Helper function to create a file with a full AvbCertUnlockChallenge struct. Arguments: product_id_hash: [optional] 32 byte value to include in the challenge as the @@ -206,7 +206,7 @@ def writeFullUnlockChallenge(out_file, product_id_hash=None): """ if product_id_hash is None: with open(DATA_FILE_PUK_CERTIFICATE, 'rb') as f: - product_id_hash = GetAtxCertificateSubject(f.read()) + product_id_hash = GetCertCertificateSubject(f.read()) assert len(product_id_hash) == 32 with open(out_file, 'wb') as out: @@ -216,7 +216,7 @@ def writeFullUnlockChallenge(out_file, product_id_hash=None): out.write(f.read()) -class MakeAtxUnlockCredentialTest(unittest.TestCase): +class MakeCertUnlockCredentialTest(unittest.TestCase): def testCredentialIsCorrect(self): with validUnlockCredsZip() as zip: @@ -230,8 +230,8 @@ class MakeAtxUnlockCredentialTest(unittest.TestCase): out_cred = os.path.join(tempdir, 'credential') # Compare unlock credential generated by function with one generated - # using 'avbtool make_atx_unlock_credential', to check correctness. - MakeAtxUnlockCredential(creds, challenge, out_cred) + # using 'avbtool make_cert_unlock_credential', to check correctness. + MakeCertUnlockCredential(creds, challenge, out_cred) self.assertTrue(filecmp.cmp(out_cred, DATA_FILE_UNLOCK_CREDENTIAL)) finally: shutil.rmtree(tempdir) @@ -245,10 +245,10 @@ class MakeAtxUnlockCredentialTest(unittest.TestCase): out_cred = os.path.join(tempdir, 'credential') # The bundled unlock challenge is just the 16 byte challenge, not the - # full AvbAtxUnlockChallenge like this expects. + # full AvbCertUnlockChallenge like this expects. with self.assertRaises(ValueError): challenge = UnlockChallenge(DATA_FILE_UNLOCK_CHALLENGE) - MakeAtxUnlockCredential(creds, challenge, out_cred) + MakeCertUnlockCredential(creds, challenge, out_cred) finally: shutil.rmtree(tempdir) diff --git a/test/avb_atx_generate_test_data b/test/avb_cert_generate_test_data index 795a2b1..38f1b8d 100755 --- a/test/avb_atx_generate_test_data +++ b/test/avb_cert_generate_test_data @@ -24,23 +24,23 @@ # SOFTWARE. # -# This shell-script generates ATX test data in the working directory. +# This shell-script generates libavb_cert test data in the working directory. # An avbtool executable is assumed to reside in the parent directory # of this script. # -# The *atx* test data in the test/data/ directory was generated with +# The *cert* test data in the test/data/ directory was generated with # this script. It is consistent with the expectations of avbtool unit -# tests and ATX unit tests. This script exists as a record of how the +# tests and avb_cert unit tests. This script exists as a record of how the # data was generated and as a convenience if it ever needs to be # generated again. # # Typical usage: # -# $ cd test/data; ../avb_atx_generate_test_data +# $ cd test/data; ../avb_cert_generate_test_data set -e -TMP_FILE=$(mktemp /tmp/atx_generator.XXXXXXXXXX) +TMP_FILE=$(mktemp /tmp/cert_generator.XXXXXXXXXX) trap "rm -f '${TMP_FILE}'" EXIT AVBTOOL=$(dirname "$0")/../avbtool.py @@ -48,58 +48,58 @@ AVBTOOL=$(dirname "$0")/../avbtool.py echo AVBTOOL = ${AVBTOOL} # Get a zero product ID. -echo 00000000000000000000000000000000 | xxd -r -p - atx_product_id.bin +echo 00000000000000000000000000000000 | xxd -r -p - cert_product_id.bin # Generate key pairs. -if [ ! -f testkey_atx_prk.pem ]; then +if [ ! -f testkey_cert_prk.pem ]; then openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM \ - -out testkey_atx_prk.pem + -out testkey_cert_prk.pem fi -if [ ! -f testkey_atx_pik.pem ]; then +if [ ! -f testkey_cert_pik.pem ]; then openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM \ - -out testkey_atx_pik.pem + -out testkey_cert_pik.pem fi -if [ ! -f testkey_atx_psk.pem ]; then +if [ ! -f testkey_cert_psk.pem ]; then openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM \ - -out testkey_atx_psk.pem + -out testkey_cert_psk.pem fi -if [ ! -f testkey_atx_puk.pem ]; then +if [ ! -f testkey_cert_puk.pem ]; then openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:4096 -outform PEM \ - -out testkey_atx_puk.pem + -out testkey_cert_puk.pem fi # Construct permanent attributes. -${AVBTOOL} make_atx_permanent_attributes --output=atx_permanent_attributes.bin \ - --product_id=atx_product_id.bin --root_authority_key=testkey_atx_prk.pem +${AVBTOOL} make_cert_permanent_attributes --output=cert_permanent_attributes.bin \ + --product_id=cert_product_id.bin --root_authority_key=testkey_cert_prk.pem # Construct a PIK certificate. echo -n "fake PIK subject" > ${TMP_FILE} -${AVBTOOL} make_atx_certificate --output=atx_pik_certificate.bin \ - --subject=${TMP_FILE} --subject_key=testkey_atx_pik.pem \ +${AVBTOOL} make_certificate --output=cert_pik_certificate.bin \ + --subject=${TMP_FILE} --subject_key=testkey_cert_pik.pem \ --subject_is_intermediate_authority --subject_key_version 42 \ - --authority_key=testkey_atx_prk.pem + --authority_key=testkey_cert_prk.pem # Construct a PSK certificate. -${AVBTOOL} make_atx_certificate --output=atx_psk_certificate.bin \ - --subject=atx_product_id.bin --subject_key=testkey_atx_psk.pem \ - --subject_key_version 42 --authority_key=testkey_atx_pik.pem +${AVBTOOL} make_certificate --output=cert_psk_certificate.bin \ + --subject=cert_product_id.bin --subject_key=testkey_cert_psk.pem \ + --subject_key_version 42 --authority_key=testkey_cert_pik.pem # Construct metadata. -${AVBTOOL} make_atx_metadata --output=atx_metadata.bin \ - --intermediate_key_certificate=atx_pik_certificate.bin \ - --product_key_certificate=atx_psk_certificate.bin +${AVBTOOL} make_cert_metadata --output=cert_metadata.bin \ + --intermediate_key_certificate=cert_pik_certificate.bin \ + --product_key_certificate=cert_psk_certificate.bin # Generate a random unlock challenge. -head -c 16 /dev/urandom > atx_unlock_challenge.bin +head -c 16 /dev/urandom > cert_unlock_challenge.bin # Construct a PUK certificate. -${AVBTOOL} make_atx_certificate --output=atx_puk_certificate.bin \ - --subject=atx_product_id.bin --subject_key=testkey_atx_puk.pem \ - --usage=com.google.android.things.vboot.unlock --subject_key_version 42 \ - --authority_key=testkey_atx_pik.pem +${AVBTOOL} make_certificate --output=cert_puk_certificate.bin \ + --subject=cert_product_id.bin --subject_key=testkey_cert_puk.pem \ + --usage_for_unlock --subject_key_version 42 \ + --authority_key=testkey_cert_pik.pem # Construct an unlock credential. -${AVBTOOL} make_atx_unlock_credential --output=atx_unlock_credential.bin \ - --intermediate_key_certificate=atx_pik_certificate.bin \ - --unlock_key_certificate=atx_puk_certificate.bin \ - --challenge=atx_unlock_challenge.bin --unlock_key=testkey_atx_puk.pem +${AVBTOOL} make_cert_unlock_credential --output=cert_unlock_credential.bin \ + --intermediate_key_certificate=cert_pik_certificate.bin \ + --unlock_key_certificate=cert_puk_certificate.bin \ + --challenge=cert_unlock_challenge.bin --unlock_key=testkey_cert_puk.pem diff --git a/test/avb_atx_slot_verify_unittest.cc b/test/avb_cert_slot_verify_unittest.cc index ff0abda..69cd412 100644 --- a/test/avb_atx_slot_verify_unittest.cc +++ b/test/avb_cert_slot_verify_unittest.cc @@ -22,41 +22,41 @@ * SOFTWARE. */ -#include <stdio.h> -#include <string.h> +#include "examples/cert/avb_cert_slot_verify.h" #include <base/files/file_util.h> #include <gtest/gtest.h> #include <openssl/sha.h> +#include <stdio.h> +#include <string.h> #include "avb_unittest_util.h" -#include "examples/things/avb_atx_slot_verify.h" #include "fake_avb_ops.h" namespace { -const char kMetadataPath[] = "test/data/atx_metadata.bin"; +const char kMetadataPath[] = "test/data/cert_metadata.bin"; const char kPermanentAttributesPath[] = - "test/data/atx_permanent_attributes.bin"; + "test/data/cert_permanent_attributes.bin"; const uint64_t kNewRollbackValue = 42; } /* namespace */ namespace avb { -// A fixture for testing avb_atx_slot_verify() with ATX. This test is +// A fixture for testing avb_cert_slot_verify() with libavb_cert. This test is // parameterized on the initial stored rollback index (same value used in all // relevant locations). -class AvbAtxSlotVerifyExampleTest +class AvbCertSlotVerifyExampleTest : public BaseAvbToolTest, public FakeAvbOpsDelegateWithDefaults, public ::testing::WithParamInterface<uint64_t> { public: - ~AvbAtxSlotVerifyExampleTest() override = default; + ~AvbCertSlotVerifyExampleTest() override = default; void SetUp() override { BaseAvbToolTest::SetUp(); - ReadAtxDefaultData(); + ReadCertDefaultData(); ops_.set_partition_dir(testdir_); ops_.set_delegate(this); ops_.set_permanent_attributes(attributes_); @@ -70,14 +70,14 @@ class AvbAtxSlotVerifyExampleTest const uint8_t* public_key_metadata, size_t public_key_metadata_length, bool* out_key_is_trusted) override { - // Send to ATX implementation. - ++num_atx_calls_; - return avb_atx_validate_vbmeta_public_key(ops, - public_key_data, - public_key_length, - public_key_metadata, - public_key_metadata_length, - out_key_is_trusted); + // Send to libavb_cert implementation. + ++num_cert_calls_; + return avb_cert_validate_vbmeta_public_key(ops, + public_key_data, + public_key_length, + public_key_metadata, + public_key_metadata_length, + out_key_is_trusted); } AvbIOResult write_rollback_index(AvbOps* ops, @@ -100,34 +100,34 @@ class AvbAtxSlotVerifyExampleTest void RunSlotVerify() { ops_.set_stored_rollback_indexes( {{0, initial_rollback_value_}, - {AVB_ATX_PIK_VERSION_LOCATION, initial_rollback_value_}, - {AVB_ATX_PSK_VERSION_LOCATION, initial_rollback_value_}}); + {AVB_CERT_PIK_VERSION_LOCATION, initial_rollback_value_}, + {AVB_CERT_PSK_VERSION_LOCATION, initial_rollback_value_}}); std::string metadata_option = "--public_key_metadata="; metadata_option += kMetadataPath; GenerateVBMetaImage("vbmeta_a.img", "SHA512_RSA4096", kNewRollbackValue, - base::FilePath("test/data/testkey_atx_psk.pem"), + base::FilePath("test/data/testkey_cert_psk.pem"), metadata_option); SHA256(vbmeta_image_.data(), vbmeta_image_.size(), expected_vbh_extension_); ops_.set_expected_public_key( - PublicKeyAVB(base::FilePath("test/data/testkey_atx_psk.pem"))); + PublicKeyAVB(base::FilePath("test/data/testkey_cert_psk.pem"))); AvbSlotVerifyData* slot_data = NULL; EXPECT_EQ(expected_result_, - avb_atx_slot_verify(ops_.avb_atx_ops(), - "_a", - lock_state_, - slot_state_, - oem_data_state_, - &slot_data, - actual_vbh_extension_)); + avb_cert_slot_verify(ops_.avb_cert_ops(), + "_a", + lock_state_, + slot_state_, + oem_data_state_, + &slot_data, + actual_vbh_extension_)); if (expected_result_ == AVB_SLOT_VERIFY_RESULT_OK) { EXPECT_NE(nullptr, slot_data); avb_slot_verify_data_free(slot_data); - // Make sure ATX is being run. - EXPECT_EQ(1, num_atx_calls_); + // Make sure libavb_cert is being run. + EXPECT_EQ(1, num_cert_calls_); // Make sure we're hooking set_key_version. EXPECT_EQ(0, num_key_version_calls_); } @@ -135,7 +135,7 @@ class AvbAtxSlotVerifyExampleTest void CheckVBH() { if (expected_result_ != AVB_SLOT_VERIFY_RESULT_OK || - lock_state_ == AVB_ATX_UNLOCKED) { + lock_state_ == AVB_CERT_UNLOCKED) { memset(&expected_vbh_extension_, 0, AVB_SHA256_DIGEST_SIZE); } // Check that the VBH was correctly calculated. @@ -148,8 +148,8 @@ class AvbAtxSlotVerifyExampleTest void CheckNewRollbackState() { uint64_t expected_rollback_value = kNewRollbackValue; if (expected_result_ != AVB_SLOT_VERIFY_RESULT_OK || - lock_state_ == AVB_ATX_UNLOCKED || - slot_state_ != AVB_ATX_SLOT_MARKED_SUCCESSFUL) { + lock_state_ == AVB_CERT_UNLOCKED || + slot_state_ != AVB_CERT_SLOT_MARKED_SUCCESSFUL) { // Check that rollback indexes were unmodified. expected_rollback_value = initial_rollback_value_; } @@ -158,9 +158,9 @@ class AvbAtxSlotVerifyExampleTest ops_.get_stored_rollback_indexes(); EXPECT_EQ(expected_rollback_value, stored_rollback_indexes[0]); EXPECT_EQ(expected_rollback_value, - stored_rollback_indexes[AVB_ATX_PIK_VERSION_LOCATION]); + stored_rollback_indexes[AVB_CERT_PIK_VERSION_LOCATION]); EXPECT_EQ(expected_rollback_value, - stored_rollback_indexes[AVB_ATX_PSK_VERSION_LOCATION]); + stored_rollback_indexes[AVB_CERT_PSK_VERSION_LOCATION]); // Check that if the rollback did not need to change, there were no writes. if (initial_rollback_value_ == kNewRollbackValue || initial_rollback_value_ == expected_rollback_value) { @@ -171,29 +171,29 @@ class AvbAtxSlotVerifyExampleTest } protected: - AvbAtxPermanentAttributes attributes_; - int num_atx_calls_ = 0; + AvbCertPermanentAttributes attributes_; + int num_cert_calls_ = 0; int num_key_version_calls_ = 0; int num_write_rollback_calls_ = 0; AvbSlotVerifyResult expected_result_ = AVB_SLOT_VERIFY_RESULT_OK; uint64_t initial_rollback_value_ = 0; - AvbAtxLockState lock_state_ = AVB_ATX_LOCKED; - AvbAtxSlotState slot_state_ = AVB_ATX_SLOT_MARKED_SUCCESSFUL; - AvbAtxOemDataState oem_data_state_ = AVB_ATX_OEM_DATA_NOT_USED; + AvbCertLockState lock_state_ = AVB_CERT_LOCKED; + AvbCertSlotState slot_state_ = AVB_CERT_SLOT_MARKED_SUCCESSFUL; + AvbCertOemDataState oem_data_state_ = AVB_CERT_OEM_DATA_NOT_USED; uint8_t expected_vbh_extension_[AVB_SHA256_DIGEST_SIZE] = {}; uint8_t actual_vbh_extension_[AVB_SHA256_DIGEST_SIZE] = {}; private: - void ReadAtxDefaultData() { + void ReadCertDefaultData() { std::string tmp; ASSERT_TRUE( base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp)); - ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes)); + ASSERT_EQ(tmp.size(), sizeof(AvbCertPermanentAttributes)); memcpy(&attributes_, tmp.data(), tmp.size()); } }; -TEST_P(AvbAtxSlotVerifyExampleTest, RunWithStartingIndex) { +TEST_P(AvbCertSlotVerifyExampleTest, RunWithStartingIndex) { initial_rollback_value_ = GetParam(); RunSlotVerify(); CheckVBH(); @@ -201,29 +201,29 @@ TEST_P(AvbAtxSlotVerifyExampleTest, RunWithStartingIndex) { } INSTANTIATE_TEST_CASE_P(P, - AvbAtxSlotVerifyExampleTest, + AvbCertSlotVerifyExampleTest, ::testing::Values(0, 1, kNewRollbackValue / 2, kNewRollbackValue - 1, kNewRollbackValue)); -TEST_F(AvbAtxSlotVerifyExampleTest, RunUnlocked) { - lock_state_ = AVB_ATX_UNLOCKED; +TEST_F(AvbCertSlotVerifyExampleTest, RunUnlocked) { + lock_state_ = AVB_CERT_UNLOCKED; RunSlotVerify(); CheckVBH(); CheckNewRollbackState(); } -TEST_F(AvbAtxSlotVerifyExampleTest, RunWithSlotNotMarkedSuccessful) { - slot_state_ = AVB_ATX_SLOT_NOT_MARKED_SUCCESSFUL; +TEST_F(AvbCertSlotVerifyExampleTest, RunWithSlotNotMarkedSuccessful) { + slot_state_ = AVB_CERT_SLOT_NOT_MARKED_SUCCESSFUL; RunSlotVerify(); CheckVBH(); CheckNewRollbackState(); } -TEST_F(AvbAtxSlotVerifyExampleTest, RunWithOemData) { - oem_data_state_ = AVB_ATX_OEM_DATA_USED; +TEST_F(AvbCertSlotVerifyExampleTest, RunWithOemData) { + oem_data_state_ = AVB_CERT_OEM_DATA_USED; RunSlotVerify(); CheckVBH(); CheckNewRollbackState(); diff --git a/test/avb_atx_validate_unittest.cc b/test/avb_cert_validate_unittest.cc index 945f6ca..63a34fe 100644 --- a/test/avb_atx_validate_unittest.cc +++ b/test/avb_cert_validate_unittest.cc @@ -22,32 +22,30 @@ * SOFTWARE. */ -#include <stdio.h> -#include <string.h> - #include <base/files/file_util.h> #include <gtest/gtest.h> +#include <libavb_cert/libavb_cert.h> #include <openssl/objects.h> #include <openssl/pem.h> #include <openssl/rsa.h> #include <openssl/sha.h> - -#include <libavb_atx/libavb_atx.h> +#include <stdio.h> +#include <string.h> #include "avb_unittest_util.h" #include "fake_avb_ops.h" namespace { -const char kMetadataPath[] = "test/data/atx_metadata.bin"; +const char kMetadataPath[] = "test/data/cert_metadata.bin"; const char kPermanentAttributesPath[] = - "test/data/atx_permanent_attributes.bin"; -const char kPRKPrivateKeyPath[] = "test/data/testkey_atx_prk.pem"; -const char kPIKPrivateKeyPath[] = "test/data/testkey_atx_pik.pem"; -const char kPSKPrivateKeyPath[] = "test/data/testkey_atx_psk.pem"; -const char kPUKPrivateKeyPath[] = "test/data/testkey_atx_puk.pem"; -const char kUnlockChallengePath[] = "test/data/atx_unlock_challenge.bin"; -const char kUnlockCredentialPath[] = "test/data/atx_unlock_credential.bin"; + "test/data/cert_permanent_attributes.bin"; +const char kPRKPrivateKeyPath[] = "test/data/testkey_cert_prk.pem"; +const char kPIKPrivateKeyPath[] = "test/data/testkey_cert_pik.pem"; +const char kPSKPrivateKeyPath[] = "test/data/testkey_cert_psk.pem"; +const char kPUKPrivateKeyPath[] = "test/data/testkey_cert_puk.pem"; +const char kUnlockChallengePath[] = "test/data/cert_unlock_challenge.bin"; +const char kUnlockCredentialPath[] = "test/data/cert_unlock_credential.bin"; class ScopedRSA { public: @@ -86,17 +84,17 @@ class ScopedRSA { namespace avb { -class AvbAtxValidateTest : public ::testing::Test, - public FakeAvbOpsDelegateWithDefaults { +class AvbCertValidateTest : public ::testing::Test, + public FakeAvbOpsDelegateWithDefaults { public: - ~AvbAtxValidateTest() override {} + ~AvbCertValidateTest() override {} void SetUp() override { ReadDefaultData(); ops_.set_delegate(this); ops_.set_permanent_attributes(attributes_); - ops_.set_stored_rollback_indexes( - {{AVB_ATX_PIK_VERSION_LOCATION, 0}, {AVB_ATX_PSK_VERSION_LOCATION, 0}}); + ops_.set_stored_rollback_indexes({{AVB_CERT_PIK_VERSION_LOCATION, 0}, + {AVB_CERT_PSK_VERSION_LOCATION, 0}}); } // FakeAvbOpsDelegate methods. @@ -140,9 +138,9 @@ class AvbAtxValidateTest : public ::testing::Test, size_t rollback_index_slot, uint64_t* out_rollback_index) override { if ((fail_read_pik_rollback_index_ && - rollback_index_slot == AVB_ATX_PIK_VERSION_LOCATION) || + rollback_index_slot == AVB_CERT_PIK_VERSION_LOCATION) || (fail_read_psk_rollback_index_ && - rollback_index_slot == AVB_ATX_PSK_VERSION_LOCATION)) { + rollback_index_slot == AVB_CERT_PSK_VERSION_LOCATION)) { return AVB_IO_RESULT_ERROR_IO; } return ops_.read_rollback_index( @@ -193,7 +191,7 @@ class AvbAtxValidateTest : public ::testing::Test, } AvbIOResult read_permanent_attributes( - AvbAtxPermanentAttributes* attributes) override { + AvbCertPermanentAttributes* attributes) override { if (fail_read_permanent_attributes_) { return AVB_IO_RESULT_ERROR_IO; } @@ -226,18 +224,18 @@ class AvbAtxValidateTest : public ::testing::Test, protected: virtual AvbIOResult Validate(bool* is_trusted) { - return avb_atx_validate_vbmeta_public_key( + return avb_cert_validate_vbmeta_public_key( ops_.avb_ops(), metadata_.product_signing_key_certificate.signed_data.public_key, - AVB_ATX_PUBLIC_KEY_SIZE, + AVB_CERT_PUBLIC_KEY_SIZE, reinterpret_cast<const uint8_t*>(&metadata_), sizeof(metadata_), is_trusted); } AvbIOResult ValidateUnlock(bool* is_trusted) { - return avb_atx_validate_unlock_credential( - ops_.avb_atx_ops(), &unlock_credential_, is_trusted); + return avb_cert_validate_unlock_credential( + ops_.avb_cert_ops(), &unlock_credential_, is_trusted); } void SignPIKCertificate() { @@ -247,7 +245,7 @@ class AvbAtxValidateTest : public ::testing::Test, ScopedRSA key(kPRKPrivateKeyPath); ASSERT_TRUE( key.Sign(&metadata_.product_intermediate_key_certificate.signed_data, - sizeof(AvbAtxCertificateSignedData), + sizeof(AvbCertCertificateSignedData), metadata_.product_intermediate_key_certificate.signature)); } @@ -257,7 +255,7 @@ class AvbAtxValidateTest : public ::testing::Test, AVB_RSA4096_NUM_BYTES); ScopedRSA key(kPIKPrivateKeyPath); ASSERT_TRUE(key.Sign(&metadata_.product_signing_key_certificate.signed_data, - sizeof(AvbAtxCertificateSignedData), + sizeof(AvbCertCertificateSignedData), metadata_.product_signing_key_certificate.signature)); } @@ -268,7 +266,7 @@ class AvbAtxValidateTest : public ::testing::Test, ScopedRSA key(kPRKPrivateKeyPath); ASSERT_TRUE(key.Sign( &unlock_credential_.product_intermediate_key_certificate.signed_data, - sizeof(AvbAtxCertificateSignedData), + sizeof(AvbCertCertificateSignedData), unlock_credential_.product_intermediate_key_certificate.signature)); } @@ -279,7 +277,7 @@ class AvbAtxValidateTest : public ::testing::Test, ScopedRSA key(kPIKPrivateKeyPath); ASSERT_TRUE( key.Sign(&unlock_credential_.product_unlock_key_certificate.signed_data, - sizeof(AvbAtxCertificateSignedData), + sizeof(AvbCertCertificateSignedData), unlock_credential_.product_unlock_key_certificate.signature)); } @@ -295,57 +293,57 @@ class AvbAtxValidateTest : public ::testing::Test, // Stage a challenge to be remembered as the 'most recent challenge'. Then // the next call to unlock with |unlock_credential_| is expected to succeed. fake_random_ = unlock_challenge_; - AvbAtxUnlockChallenge challenge; - return (AVB_IO_RESULT_OK == - avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge)); + AvbCertUnlockChallenge challenge; + return (AVB_IO_RESULT_OK == avb_cert_generate_unlock_challenge( + ops_.avb_cert_ops(), &challenge)); } - AvbAtxPermanentAttributes attributes_; - AvbAtxPublicKeyMetadata metadata_; + AvbCertPermanentAttributes attributes_; + AvbCertPublicKeyMetadata metadata_; bool fail_read_permanent_attributes_{false}; bool fail_read_permanent_attributes_hash_{false}; bool fail_read_pik_rollback_index_{false}; bool fail_read_psk_rollback_index_{false}; bool fail_get_random_{false}; std::string fake_random_; - AvbAtxUnlockCredential unlock_credential_; + AvbCertUnlockCredential unlock_credential_; std::string unlock_challenge_; private: void ReadDefaultData() { std::string tmp; ASSERT_TRUE(base::ReadFileToString(base::FilePath(kMetadataPath), &tmp)); - ASSERT_EQ(tmp.size(), sizeof(AvbAtxPublicKeyMetadata)); + ASSERT_EQ(tmp.size(), sizeof(AvbCertPublicKeyMetadata)); memcpy(&metadata_, tmp.data(), tmp.size()); ASSERT_TRUE( base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp)); - ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes)); + ASSERT_EQ(tmp.size(), sizeof(AvbCertPermanentAttributes)); memcpy(&attributes_, tmp.data(), tmp.size()); ASSERT_TRUE(base::ReadFileToString(base::FilePath(kUnlockChallengePath), &unlock_challenge_)); - ASSERT_EQ(size_t(AVB_ATX_UNLOCK_CHALLENGE_SIZE), unlock_challenge_.size()); + ASSERT_EQ(size_t(AVB_CERT_UNLOCK_CHALLENGE_SIZE), unlock_challenge_.size()); ASSERT_TRUE( base::ReadFileToString(base::FilePath(kUnlockCredentialPath), &tmp)); - ASSERT_EQ(tmp.size(), sizeof(AvbAtxUnlockCredential)); + ASSERT_EQ(tmp.size(), sizeof(AvbCertUnlockCredential)); memcpy(&unlock_credential_, tmp.data(), tmp.size()); } }; -TEST_F(AvbAtxValidateTest, Success) { +TEST_F(AvbCertValidateTest, Success) { bool is_trusted = false; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_TRUE(is_trusted); // Check that the key versions were reported correctly. EXPECT_EQ( - ops_.get_verified_rollback_indexes()[AVB_ATX_PIK_VERSION_LOCATION], + ops_.get_verified_rollback_indexes()[AVB_CERT_PIK_VERSION_LOCATION], metadata_.product_intermediate_key_certificate.signed_data.key_version); - EXPECT_EQ(ops_.get_verified_rollback_indexes()[AVB_ATX_PSK_VERSION_LOCATION], + EXPECT_EQ(ops_.get_verified_rollback_indexes()[AVB_CERT_PSK_VERSION_LOCATION], metadata_.product_signing_key_certificate.signed_data.key_version); EXPECT_EQ(2UL, ops_.get_verified_rollback_indexes().size()); } -TEST_F(AvbAtxValidateTest, SuccessAfterNewSign) { +TEST_F(AvbCertValidateTest, SuccessAfterNewSign) { std::string old_pik_sig( reinterpret_cast<char*>( metadata_.product_intermediate_key_certificate.signature), @@ -371,21 +369,21 @@ TEST_F(AvbAtxValidateTest, SuccessAfterNewSign) { EXPECT_TRUE(is_trusted); } -TEST_F(AvbAtxValidateTest, FailReadPermamentAttributes) { +TEST_F(AvbCertValidateTest, FailReadPermamentAttributes) { fail_read_permanent_attributes_ = true; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, FailReadPermamentAttributesHash) { +TEST_F(AvbCertValidateTest, FailReadPermamentAttributesHash) { fail_read_permanent_attributes_hash_ = true; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, UnsupportedPermanentAttributesVersion) { +TEST_F(AvbCertValidateTest, UnsupportedPermanentAttributesVersion) { attributes_.version = 25; ops_.set_permanent_attributes(attributes_); bool is_trusted = true; @@ -393,7 +391,7 @@ TEST_F(AvbAtxValidateTest, UnsupportedPermanentAttributesVersion) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, PermanentAttributesHashMismatch) { +TEST_F(AvbCertValidateTest, PermanentAttributesHashMismatch) { ops_.set_permanent_attributes_hash("bad_hash"); bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); @@ -401,22 +399,22 @@ TEST_F(AvbAtxValidateTest, PermanentAttributesHashMismatch) { } // A fixture with parameterized metadata length. -class AvbAtxValidateTestWithMetadataLength - : public AvbAtxValidateTest, +class AvbCertValidateTestWithMetadataLength + : public AvbCertValidateTest, public ::testing::WithParamInterface<size_t> { protected: AvbIOResult Validate(bool* is_trusted) override { - return avb_atx_validate_vbmeta_public_key( + return avb_cert_validate_vbmeta_public_key( ops_.avb_ops(), metadata_.product_signing_key_certificate.signed_data.public_key, - AVB_ATX_PUBLIC_KEY_SIZE, + AVB_CERT_PUBLIC_KEY_SIZE, reinterpret_cast<const uint8_t*>(&metadata_), GetParam(), is_trusted); } }; -TEST_P(AvbAtxValidateTestWithMetadataLength, InvalidMetadataLength) { +TEST_P(AvbCertValidateTestWithMetadataLength, InvalidMetadataLength) { bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); @@ -424,28 +422,28 @@ TEST_P(AvbAtxValidateTestWithMetadataLength, InvalidMetadataLength) { // Test a bunch of invalid metadata length values. INSTANTIATE_TEST_CASE_P(P, - AvbAtxValidateTestWithMetadataLength, + AvbCertValidateTestWithMetadataLength, ::testing::Values(0, 1, - sizeof(AvbAtxPublicKeyMetadata) - 1, - sizeof(AvbAtxPublicKeyMetadata) + 1, + sizeof(AvbCertPublicKeyMetadata) - 1, + sizeof(AvbCertPublicKeyMetadata) + 1, -1)); -TEST_F(AvbAtxValidateTest, UnsupportedMetadataVersion) { +TEST_F(AvbCertValidateTest, UnsupportedMetadataVersion) { metadata_.version = 25; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, FailReadPIKRollbackIndex) { +TEST_F(AvbCertValidateTest, FailReadPIKRollbackIndex) { fail_read_pik_rollback_index_ = true; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, UnsupportedPIKCertificateVersion) { +TEST_F(AvbCertValidateTest, UnsupportedPIKCertificateVersion) { metadata_.product_intermediate_key_certificate.signed_data.version = 25; SignPIKCertificate(); bool is_trusted = true; @@ -453,42 +451,42 @@ TEST_F(AvbAtxValidateTest, UnsupportedPIKCertificateVersion) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedSubjectPublicKey) { +TEST_F(AvbCertValidateTest, BadPIKCert_ModifiedSubjectPublicKey) { metadata_.product_intermediate_key_certificate.signed_data.public_key[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedSubject) { +TEST_F(AvbCertValidateTest, BadPIKCert_ModifiedSubject) { metadata_.product_intermediate_key_certificate.signed_data.subject[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedUsage) { +TEST_F(AvbCertValidateTest, BadPIKCert_ModifiedUsage) { metadata_.product_intermediate_key_certificate.signed_data.usage[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPIKCert_ModifiedKeyVersion) { +TEST_F(AvbCertValidateTest, BadPIKCert_ModifiedKeyVersion) { metadata_.product_intermediate_key_certificate.signed_data.key_version ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPIKCert_BadSignature) { +TEST_F(AvbCertValidateTest, BadPIKCert_BadSignature) { metadata_.product_intermediate_key_certificate.signature[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, PIKCertSubjectIgnored) { +TEST_F(AvbCertValidateTest, PIKCertSubjectIgnored) { metadata_.product_intermediate_key_certificate.signed_data.subject[0] ^= 1; SignPIKCertificate(); bool is_trusted = false; @@ -496,7 +494,7 @@ TEST_F(AvbAtxValidateTest, PIKCertSubjectIgnored) { EXPECT_TRUE(is_trusted); } -TEST_F(AvbAtxValidateTest, PIKCertUnexpectedUsage) { +TEST_F(AvbCertValidateTest, PIKCertUnexpectedUsage) { metadata_.product_intermediate_key_certificate.signed_data.usage[0] ^= 1; SignPIKCertificate(); bool is_trusted = true; @@ -504,25 +502,25 @@ TEST_F(AvbAtxValidateTest, PIKCertUnexpectedUsage) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, PIKRollback) { +TEST_F(AvbCertValidateTest, PIKRollback) { ops_.set_stored_rollback_indexes( - {{AVB_ATX_PIK_VERSION_LOCATION, + {{AVB_CERT_PIK_VERSION_LOCATION, metadata_.product_intermediate_key_certificate.signed_data.key_version + 1}, - {AVB_ATX_PSK_VERSION_LOCATION, 0}}); + {AVB_CERT_PSK_VERSION_LOCATION, 0}}); bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, FailReadPSKRollbackIndex) { +TEST_F(AvbCertValidateTest, FailReadPSKRollbackIndex) { fail_read_psk_rollback_index_ = true; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_ERROR_IO, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, UnsupportedPSKCertificateVersion) { +TEST_F(AvbCertValidateTest, UnsupportedPSKCertificateVersion) { metadata_.product_signing_key_certificate.signed_data.version = 25; SignPSKCertificate(); bool is_trusted = true; @@ -530,42 +528,42 @@ TEST_F(AvbAtxValidateTest, UnsupportedPSKCertificateVersion) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedSubjectPublicKey) { +TEST_F(AvbCertValidateTest, BadPSKCert_ModifiedSubjectPublicKey) { metadata_.product_signing_key_certificate.signed_data.public_key[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedSubject) { +TEST_F(AvbCertValidateTest, BadPSKCert_ModifiedSubject) { metadata_.product_signing_key_certificate.signed_data.subject[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedUsage) { +TEST_F(AvbCertValidateTest, BadPSKCert_ModifiedUsage) { metadata_.product_signing_key_certificate.signed_data.usage[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPSKCert_ModifiedKeyVersion) { +TEST_F(AvbCertValidateTest, BadPSKCert_ModifiedKeyVersion) { metadata_.product_signing_key_certificate.signed_data.key_version ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, BadPSKCert_BadSignature) { +TEST_F(AvbCertValidateTest, BadPSKCert_BadSignature) { metadata_.product_signing_key_certificate.signature[0] ^= 1; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, PSKCertUnexpectedSubject) { +TEST_F(AvbCertValidateTest, PSKCertUnexpectedSubject) { metadata_.product_signing_key_certificate.signed_data.subject[0] ^= 1; SignPSKCertificate(); bool is_trusted = true; @@ -573,7 +571,7 @@ TEST_F(AvbAtxValidateTest, PSKCertUnexpectedSubject) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, PSKCertUnexpectedUsage) { +TEST_F(AvbCertValidateTest, PSKCertUnexpectedUsage) { metadata_.product_signing_key_certificate.signed_data.usage[0] ^= 1; SignPSKCertificate(); bool is_trusted = true; @@ -581,10 +579,10 @@ TEST_F(AvbAtxValidateTest, PSKCertUnexpectedUsage) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, PSKRollback) { +TEST_F(AvbCertValidateTest, PSKRollback) { ops_.set_stored_rollback_indexes( - {{AVB_ATX_PIK_VERSION_LOCATION, 0}, - {AVB_ATX_PSK_VERSION_LOCATION, + {{AVB_CERT_PIK_VERSION_LOCATION, 0}, + {AVB_CERT_PSK_VERSION_LOCATION, metadata_.product_signing_key_certificate.signed_data.key_version + 1}}); bool is_trusted = true; @@ -593,12 +591,12 @@ TEST_F(AvbAtxValidateTest, PSKRollback) { } // A fixture with parameterized public key length. -class AvbAtxValidateTestWithPublicKeyLength - : public AvbAtxValidateTest, +class AvbCertValidateTestWithPublicKeyLength + : public AvbCertValidateTest, public ::testing::WithParamInterface<size_t> { protected: AvbIOResult Validate(bool* is_trusted) override { - return avb_atx_validate_vbmeta_public_key( + return avb_cert_validate_vbmeta_public_key( ops_.avb_ops(), metadata_.product_signing_key_certificate.signed_data.public_key, GetParam(), @@ -608,7 +606,7 @@ class AvbAtxValidateTestWithPublicKeyLength } }; -TEST_P(AvbAtxValidateTestWithPublicKeyLength, InvalidPublicKeyLength) { +TEST_P(AvbCertValidateTestWithPublicKeyLength, InvalidPublicKeyLength) { bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, Validate(&is_trusted)); EXPECT_FALSE(is_trusted); @@ -616,68 +614,71 @@ TEST_P(AvbAtxValidateTestWithPublicKeyLength, InvalidPublicKeyLength) { // Test a bunch of invalid public key length values. INSTANTIATE_TEST_CASE_P(P, - AvbAtxValidateTestWithPublicKeyLength, + AvbCertValidateTestWithPublicKeyLength, ::testing::Values(0, 1, - AVB_ATX_PUBLIC_KEY_SIZE - 1, - AVB_ATX_PUBLIC_KEY_SIZE + 1, - AVB_ATX_PUBLIC_KEY_SIZE - 512, + AVB_CERT_PUBLIC_KEY_SIZE - 1, + AVB_CERT_PUBLIC_KEY_SIZE + 1, + AVB_CERT_PUBLIC_KEY_SIZE - 512, -1)); -TEST_F(AvbAtxValidateTest, PSKMismatch) { - uint8_t bad_key[AVB_ATX_PUBLIC_KEY_SIZE] = {}; +TEST_F(AvbCertValidateTest, PSKMismatch) { + uint8_t bad_key[AVB_CERT_PUBLIC_KEY_SIZE] = {}; bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, - avb_atx_validate_vbmeta_public_key( + avb_cert_validate_vbmeta_public_key( ops_.avb_ops(), bad_key, - AVB_ATX_PUBLIC_KEY_SIZE, + AVB_CERT_PUBLIC_KEY_SIZE, reinterpret_cast<const uint8_t*>(&metadata_), sizeof(metadata_), &is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, GenerateUnlockChallenge) { - fake_random_ = std::string(AVB_ATX_UNLOCK_CHALLENGE_SIZE, 'C'); - AvbAtxUnlockChallenge challenge; - EXPECT_EQ(AVB_IO_RESULT_OK, - avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge)); +TEST_F(AvbCertValidateTest, GenerateUnlockChallenge) { + fake_random_ = std::string(AVB_CERT_UNLOCK_CHALLENGE_SIZE, 'C'); + AvbCertUnlockChallenge challenge; + EXPECT_EQ( + AVB_IO_RESULT_OK, + avb_cert_generate_unlock_challenge(ops_.avb_cert_ops(), &challenge)); EXPECT_EQ(1UL, challenge.version); EXPECT_EQ(0, memcmp(fake_random_.data(), challenge.challenge, - AVB_ATX_UNLOCK_CHALLENGE_SIZE)); + AVB_CERT_UNLOCK_CHALLENGE_SIZE)); uint8_t expected_pid_hash[AVB_SHA256_DIGEST_SIZE]; - SHA256(attributes_.product_id, AVB_ATX_PRODUCT_ID_SIZE, expected_pid_hash); + SHA256(attributes_.product_id, AVB_CERT_PRODUCT_ID_SIZE, expected_pid_hash); EXPECT_EQ(0, memcmp(expected_pid_hash, challenge.product_id_hash, AVB_SHA256_DIGEST_SIZE)); } -TEST_F(AvbAtxValidateTest, GenerateUnlockChallenge_NoAttributes) { +TEST_F(AvbCertValidateTest, GenerateUnlockChallenge_NoAttributes) { fail_read_permanent_attributes_ = true; - AvbAtxUnlockChallenge challenge; - EXPECT_NE(AVB_IO_RESULT_OK, - avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge)); + AvbCertUnlockChallenge challenge; + EXPECT_NE( + AVB_IO_RESULT_OK, + avb_cert_generate_unlock_challenge(ops_.avb_cert_ops(), &challenge)); } -TEST_F(AvbAtxValidateTest, GenerateUnlockChallenge_NoRNG) { +TEST_F(AvbCertValidateTest, GenerateUnlockChallenge_NoRNG) { fail_get_random_ = true; - AvbAtxUnlockChallenge challenge; - EXPECT_NE(AVB_IO_RESULT_OK, - avb_atx_generate_unlock_challenge(ops_.avb_atx_ops(), &challenge)); + AvbCertUnlockChallenge challenge; + EXPECT_NE( + AVB_IO_RESULT_OK, + avb_cert_generate_unlock_challenge(ops_.avb_cert_ops(), &challenge)); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential) { ASSERT_TRUE(PrepareUnlockCredential()); bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted)); EXPECT_TRUE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_UnsupportedVersion) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_UnsupportedVersion) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.version++; bool is_trusted = true; @@ -685,7 +686,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_UnsupportedVersion) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_NoAttributes) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_NoAttributes) { PrepareUnlockCredential(); fail_read_permanent_attributes_ = true; bool is_trusted = true; @@ -693,7 +694,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_NoAttributes) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_NoAttributesHash) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_NoAttributesHash) { PrepareUnlockCredential(); fail_read_permanent_attributes_hash_ = true; bool is_trusted = true; @@ -701,7 +702,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_NoAttributesHash) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_UnsupportedAttributesVersion) { ASSERT_TRUE(PrepareUnlockCredential()); attributes_.version = 25; @@ -711,7 +712,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_AttributesHashMismatch) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_AttributesHashMismatch) { ASSERT_TRUE(PrepareUnlockCredential()); ops_.set_permanent_attributes_hash("bad_hash"); bool is_trusted = true; @@ -719,7 +720,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_AttributesHashMismatch) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_FailReadPIKRollbackIndex) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_FailReadPIKRollbackIndex) { ASSERT_TRUE(PrepareUnlockCredential()); fail_read_pik_rollback_index_ = true; bool is_trusted = true; @@ -727,7 +728,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_FailReadPIKRollbackIndex) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_UnsupportedPIKCertificateVersion) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signed_data.version = @@ -738,7 +739,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPIKCert_ModifiedSubjectPublicKey) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signed_data @@ -748,7 +749,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPIKCert_ModifiedSubject) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signed_data @@ -758,7 +759,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPIKCert_ModifiedUsage) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPIKCert_ModifiedUsage) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signed_data .usage[0] ^= 1; @@ -767,7 +768,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPIKCert_ModifiedUsage) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPIKCert_ModifiedKeyVersion) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signed_data @@ -777,7 +778,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPIKCert_BadSignature) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPIKCert_BadSignature) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signature[0] ^= 1; bool is_trusted = true; @@ -785,7 +786,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPIKCert_BadSignature) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKCertSubjectIgnored) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_PIKCertSubjectIgnored) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signed_data .subject[0] ^= 1; @@ -795,7 +796,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKCertSubjectIgnored) { EXPECT_TRUE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKCertUnexpectedUsage) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_PIKCertUnexpectedUsage) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_intermediate_key_certificate.signed_data .usage[0] ^= 1; @@ -805,20 +806,20 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKCertUnexpectedUsage) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PIKRollback) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_PIKRollback) { ASSERT_TRUE(PrepareUnlockCredential()); ops_.set_stored_rollback_indexes( - {{AVB_ATX_PIK_VERSION_LOCATION, + {{AVB_CERT_PIK_VERSION_LOCATION, unlock_credential_.product_intermediate_key_certificate.signed_data .key_version + 1}, - {AVB_ATX_PSK_VERSION_LOCATION, 0}}); + {AVB_CERT_PSK_VERSION_LOCATION, 0}}); bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted)); EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_FailReadPSKRollbackIndex) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_FailReadPSKRollbackIndex) { ASSERT_TRUE(PrepareUnlockCredential()); fail_read_psk_rollback_index_ = true; bool is_trusted = true; @@ -826,7 +827,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_FailReadPSKRollbackIndex) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_UnsupportedPUKCertificateVersion) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signed_data.version = 25; @@ -836,7 +837,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPUKCert_ModifiedSubjectPublicKey) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signed_data.public_key[0] ^= @@ -846,7 +847,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPUKCert_ModifiedSubject) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signed_data.subject[0] ^= 1; @@ -855,7 +856,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPUKCert_ModifiedUsage) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPUKCert_ModifiedUsage) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signed_data.usage[0] ^= 1; bool is_trusted = true; @@ -863,7 +864,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPUKCert_ModifiedUsage) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPUKCert_ModifiedKeyVersion) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signed_data.key_version ^= @@ -873,7 +874,7 @@ TEST_F(AvbAtxValidateTest, EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPUKCert_BadSignature) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadPUKCert_BadSignature) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signature[0] ^= 1; bool is_trusted = true; @@ -881,7 +882,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadPUKCert_BadSignature) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKCertUnexpectedSubject) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_PUKCertUnexpectedSubject) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signed_data.subject[0] ^= 1; SignUnlockCredentialPUKCertificate(); @@ -890,7 +891,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKCertUnexpectedSubject) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKCertUnexpectedUsage) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_PUKCertUnexpectedUsage) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.product_unlock_key_certificate.signed_data.usage[0] ^= 1; SignUnlockCredentialPUKCertificate(); @@ -899,11 +900,11 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKCertUnexpectedUsage) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKRollback) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_PUKRollback) { ASSERT_TRUE(PrepareUnlockCredential()); ops_.set_stored_rollback_indexes( - {{AVB_ATX_PIK_VERSION_LOCATION, 0}, - {AVB_ATX_PSK_VERSION_LOCATION, + {{AVB_CERT_PIK_VERSION_LOCATION, 0}, + {AVB_CERT_PSK_VERSION_LOCATION, unlock_credential_.product_unlock_key_certificate.signed_data .key_version + 1}}); @@ -912,7 +913,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_PUKRollback) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadChallengeSignature) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_BadChallengeSignature) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_credential_.challenge_signature[10] ^= 1; bool is_trusted = true; @@ -920,7 +921,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_BadChallengeSignature) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_ChallengeMismatch) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_ChallengeMismatch) { ASSERT_TRUE(PrepareUnlockCredential()); unlock_challenge_ = "bad"; SignUnlockCredentialChallenge(kPUKPrivateKeyPath); @@ -929,12 +930,12 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_ChallengeMismatch) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_UnlockWithPSK) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_UnlockWithPSK) { ASSERT_TRUE(PrepareUnlockCredential()); // Copy the PSK cert as the PUK cert. memcpy(&unlock_credential_.product_unlock_key_certificate, &metadata_.product_signing_key_certificate, - sizeof(AvbAtxCertificate)); + sizeof(AvbCertCertificate)); // Sign the challenge with the PSK instead of the PUK. SignUnlockCredentialChallenge(kPSKPrivateKeyPath); bool is_trusted = true; @@ -942,7 +943,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_UnlockWithPSK) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_ReplayChallenge) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_ReplayChallenge) { ASSERT_TRUE(PrepareUnlockCredential()); bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted)); @@ -952,7 +953,7 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_ReplayChallenge) { EXPECT_FALSE(is_trusted); } -TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_MultipleUnlock) { +TEST_F(AvbCertValidateTest, ValidateUnlockCredential_MultipleUnlock) { ASSERT_TRUE(PrepareUnlockCredential()); bool is_trusted = true; EXPECT_EQ(AVB_IO_RESULT_OK, ValidateUnlock(&is_trusted)); @@ -963,15 +964,15 @@ TEST_F(AvbAtxValidateTest, ValidateUnlockCredential_MultipleUnlock) { EXPECT_TRUE(is_trusted); } -// A fixture for testing avb_slot_verify() with ATX. -class AvbAtxSlotVerifyTest : public BaseAvbToolTest, - public FakeAvbOpsDelegateWithDefaults { +// A fixture for testing avb_slot_verify() with libavb_cert. +class AvbCertSlotVerifyTest : public BaseAvbToolTest, + public FakeAvbOpsDelegateWithDefaults { public: - ~AvbAtxSlotVerifyTest() override = default; + ~AvbCertSlotVerifyTest() override = default; void SetUp() override { BaseAvbToolTest::SetUp(); - ReadAtxDefaultData(); + ReadCertDefaultData(); ops_.set_partition_dir(testdir_); ops_.set_delegate(this); ops_.set_permanent_attributes(attributes_); @@ -979,8 +980,8 @@ class AvbAtxSlotVerifyTest : public BaseAvbToolTest, {1, 0}, {2, 0}, {3, 0}, - {AVB_ATX_PIK_VERSION_LOCATION, 0}, - {AVB_ATX_PSK_VERSION_LOCATION, 0}}); + {AVB_CERT_PIK_VERSION_LOCATION, 0}, + {AVB_CERT_PSK_VERSION_LOCATION, 0}}); ops_.set_stored_is_device_unlocked(false); } @@ -991,41 +992,41 @@ class AvbAtxSlotVerifyTest : public BaseAvbToolTest, const uint8_t* public_key_metadata, size_t public_key_metadata_length, bool* out_key_is_trusted) override { - // Send to ATX implementation. - ++num_atx_calls_; - return avb_atx_validate_vbmeta_public_key(ops_.avb_ops(), - public_key_data, - public_key_length, - public_key_metadata, - public_key_metadata_length, - out_key_is_trusted); + // Send to libavb_cert implementation. + ++num_cert_calls_; + return avb_cert_validate_vbmeta_public_key(ops_.avb_ops(), + public_key_data, + public_key_length, + public_key_metadata, + public_key_metadata_length, + out_key_is_trusted); } protected: - AvbAtxPermanentAttributes attributes_; - int num_atx_calls_ = 0; + AvbCertPermanentAttributes attributes_; + int num_cert_calls_ = 0; private: - void ReadAtxDefaultData() { + void ReadCertDefaultData() { std::string tmp; ASSERT_TRUE( base::ReadFileToString(base::FilePath(kPermanentAttributesPath), &tmp)); - ASSERT_EQ(tmp.size(), sizeof(AvbAtxPermanentAttributes)); + ASSERT_EQ(tmp.size(), sizeof(AvbCertPermanentAttributes)); memcpy(&attributes_, tmp.data(), tmp.size()); } }; -TEST_F(AvbAtxSlotVerifyTest, SlotVerifyWithAtx) { +TEST_F(AvbCertSlotVerifyTest, SlotVerifyWithCert) { std::string metadata_option = "--public_key_metadata="; metadata_option += kMetadataPath; GenerateVBMetaImage("vbmeta_a.img", "SHA512_RSA4096", 0, - base::FilePath("test/data/testkey_atx_psk.pem"), + base::FilePath("test/data/testkey_cert_psk.pem"), metadata_option); ops_.set_expected_public_key( - PublicKeyAVB(base::FilePath("test/data/testkey_atx_psk.pem"))); + PublicKeyAVB(base::FilePath("test/data/testkey_cert_psk.pem"))); AvbSlotVerifyData* slot_data = NULL; const char* requested_partitions[] = {"boot", NULL}; @@ -1038,7 +1039,7 @@ TEST_F(AvbAtxSlotVerifyTest, SlotVerifyWithAtx) { &slot_data)); EXPECT_NE(nullptr, slot_data); avb_slot_verify_data_free(slot_data); - EXPECT_EQ(1, num_atx_calls_); + EXPECT_EQ(1, num_cert_calls_); } } // namespace avb diff --git a/test/avb_slot_verify_unittest.cc b/test/avb_slot_verify_unittest.cc index 9745308..9360155 100644 --- a/test/avb_slot_verify_unittest.cc +++ b/test/avb_slot_verify_unittest.cc @@ -75,7 +75,7 @@ TEST_F(AvbSlotVerifyTest, Basic) { EXPECT_NE(nullptr, slot_data); EXPECT_EQ( "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 " "androidboot.vbmeta.digest=" @@ -116,7 +116,7 @@ TEST_F(AvbSlotVerifyTest, BasicSha512) { EXPECT_NE(nullptr, slot_data); EXPECT_EQ( "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha512 androidboot.vbmeta.size=1152 " "androidboot.vbmeta.digest=" @@ -164,7 +164,7 @@ TEST_F(AvbSlotVerifyTest, BasicUnlocked) { EXPECT_NE(nullptr, slot_data); EXPECT_EQ( "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=unlocked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 " "androidboot.vbmeta.digest=" @@ -738,7 +738,7 @@ TEST_F(AvbSlotVerifyTest, HashDescriptorInVBMeta) { "cmdline in vbmeta 1234-fake-guid-for:boot_a cmdline in hash footer " "1234-fake-guid-for:system_a " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1472 " "androidboot.vbmeta.digest=" @@ -1015,6 +1015,7 @@ TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartition) { " Rollback Index Location: 1\n" " Public key (sha1): " "2597c218aae470a130f61162feaae70afd97f011\n" + " Flags: 0\n" " Kernel Cmdline descriptor:\n" " Flags: 0\n" " Kernel Cmdline: 'cmdline2 in vbmeta'\n", @@ -1122,7 +1123,7 @@ TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartition) { EXPECT_EQ( "cmdline2 in hash footer cmdline2 in vbmeta " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 " "androidboot.vbmeta.digest=" @@ -1146,6 +1147,166 @@ TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartition) { CalcVBMetaDigest("vbmeta.img", "sha256")); } +TEST_F(AvbSlotVerifyTest, HashDescriptorInChainedPartitionNoAB) { + size_t boot_partition_size = 16 * 1024 * 1024; + const size_t boot_image_size = 5 * 1024 * 1024; + base::FilePath boot_path = GenerateImage("boot.img", boot_image_size); + EXPECT_COMMAND(0, + "./avbtool.py add_hash_footer" + " --image %s" + " --rollback_index 12" + " --partition_name boot" + " --partition_size %zd" + " --algorithm SHA256_RSA4096" + " --key test/data/testkey_rsa4096.pem" + " --salt d70fd60d0f7d9c3b4587b9782c0dd2012ba01bfb3598a47ca8dce88d6afb9415" + " --internal_release_string \"\"" + " --do_not_use_ab", + boot_path.value().c_str(), + boot_partition_size); + + base::FilePath pk_path = testdir_.Append("testkey_rsa4096.avbpubkey"); + EXPECT_COMMAND( + 0, + "./avbtool.py extract_public_key --key test/data/testkey_rsa4096.pem" + " --output %s", + pk_path.value().c_str()); + + GenerateVBMetaImage( + "vbmeta_a.img", + "SHA256_RSA2048", + 11, + base::FilePath("test/data/testkey_rsa2048.pem"), + base::StringPrintf("--chain_partition_do_not_use_ab boot:1:%s" + " --internal_release_string \"\"", + pk_path.value().c_str())); + + EXPECT_EQ( + "Minimum libavb version: 1.3\n" + "Header Block: 256 bytes\n" + "Authentication Block: 320 bytes\n" + "Auxiliary Block: 1664 bytes\n" + "Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" + "Algorithm: SHA256_RSA2048\n" + "Rollback Index: 11\n" + "Flags: 0\n" + "Rollback Index Location: 0\n" + "Release String: ''\n" + "Descriptors:\n" + " Chain Partition descriptor:\n" + " Partition Name: boot\n" + " Rollback Index Location: 1\n" + " Public key (sha1): " + "2597c218aae470a130f61162feaae70afd97f011\n" + " Flags: 1\n", + InfoImage(vbmeta_image_path_)); + + EXPECT_EQ( + "Footer version: 1.0\n" + "Image size: 16777216 bytes\n" + "Original image size: 5242880 bytes\n" + "VBMeta offset: 5242880\n" + "VBMeta size: 2112 bytes\n" + "--\n" + "Minimum libavb version: 1.1\n" + "Header Block: 256 bytes\n" + "Authentication Block: 576 bytes\n" + "Auxiliary Block: 1280 bytes\n" + "Public key (sha1): 2597c218aae470a130f61162feaae70afd97f011\n" + "Algorithm: SHA256_RSA4096\n" + "Rollback Index: 12\n" + "Flags: 0\n" + "Rollback Index Location: 0\n" + "Release String: ''\n" + "Descriptors:\n" + " Hash descriptor:\n" + " Image Size: 5242880 bytes\n" + " Hash Algorithm: sha256\n" + " Partition Name: boot\n" + " Salt: " + "d70fd60d0f7d9c3b4587b9782c0dd2012ba01bfb3598a47ca8dce88d6afb9415\n" + " Digest: " + "e54d3d497d1cb01bd9b0a2ebda4a305b12b4f5ba084c9cc588690d33ae1e9940\n" + " Flags: 1\n", + InfoImage(boot_path)); + + ops_.set_expected_public_key( + PublicKeyAVB(base::FilePath("test/data/testkey_rsa2048.pem"))); + + AvbSlotVerifyData* slot_data = NULL; + const char* requested_partitions[] = {"boot", NULL}; + EXPECT_EQ(AVB_SLOT_VERIFY_RESULT_OK, + avb_slot_verify(ops_.avb_ops(), + requested_partitions, + "_a", + AVB_SLOT_VERIFY_FLAGS_NONE, + AVB_HASHTREE_ERROR_MODE_RESTART_AND_INVALIDATE, + &slot_data)); + EXPECT_NE(nullptr, slot_data); + + // Now verify the slot data. The vbmeta data should match our + // vbmeta_image_ member. + EXPECT_EQ(size_t(2), slot_data->num_vbmeta_images); + EXPECT_EQ(slot_data->vbmeta_images[0].vbmeta_size, vbmeta_image_.size()); + EXPECT_EQ(0, + memcmp(vbmeta_image_.data(), + slot_data->vbmeta_images[0].vbmeta_data, + slot_data->vbmeta_images[0].vbmeta_size)); + + // And for the second vbmeta struct we check that the descriptors + // match the info_image output from above. + EXPECT_EQ("boot", std::string(slot_data->vbmeta_images[1].partition_name)); + const AvbDescriptor** descriptors = + avb_descriptor_get_all(slot_data->vbmeta_images[1].vbmeta_data, + slot_data->vbmeta_images[1].vbmeta_size, + NULL); + EXPECT_NE(nullptr, descriptors); + AvbHashDescriptor hash_desc; + EXPECT_EQ(true, + avb_hash_descriptor_validate_and_byteswap( + ((AvbHashDescriptor*)descriptors[0]), &hash_desc)); + const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) + + sizeof(AvbHashDescriptor); + uint64_t o = 0; + EXPECT_EQ("boot", + std::string(reinterpret_cast<const char*>(desc_end + o), + hash_desc.partition_name_len)); + o += hash_desc.partition_name_len; + EXPECT_EQ("d70fd60d0f7d9c3b4587b9782c0dd2012ba01bfb3598a47ca8dce88d6afb9415", + mem_to_hexstring(desc_end + o, hash_desc.salt_len)); + o += hash_desc.salt_len; + EXPECT_EQ("e54d3d497d1cb01bd9b0a2ebda4a305b12b4f5ba084c9cc588690d33ae1e9940", + mem_to_hexstring(desc_end + o, hash_desc.digest_len)); + avb_free(descriptors); + + // The boot image data should match what is generated above with + // GenerateImage(). + EXPECT_EQ(size_t(1), slot_data->num_loaded_partitions); + EXPECT_EQ("boot", + std::string(slot_data->loaded_partitions[0].partition_name)); + EXPECT_EQ(boot_image_size, slot_data->loaded_partitions[0].data_size); + for (size_t n = 0; n < slot_data->loaded_partitions[0].data_size; n++) { + EXPECT_EQ(slot_data->loaded_partitions[0].data[n], uint8_t(n)); + } + + EXPECT_EQ(11UL, slot_data->rollback_indexes[0]); + EXPECT_EQ(12UL, slot_data->rollback_indexes[1]); + for (size_t n = 2; n < AVB_MAX_NUMBER_OF_ROLLBACK_INDEX_LOCATIONS; n++) { + EXPECT_EQ(0UL, slot_data->rollback_indexes[n]); + } + + uint8_t vbmeta_digest[AVB_SHA256_DIGEST_SIZE]; + avb_slot_verify_data_calculate_vbmeta_digest( + slot_data, AVB_DIGEST_TYPE_SHA256, vbmeta_digest); + EXPECT_EQ("40adb9e4d7a21cbba5c48ce540c370405e83af3355588aa108db5347b8459d71", + mem_to_hexstring(vbmeta_digest, AVB_SHA256_DIGEST_SIZE)); + + EXPECT_EQ("40adb9e4d7a21cbba5c48ce540c370405e83af3355588aa108db5347b8459d71", + CalcVBMetaDigest("vbmeta_a.img", "sha256")); + + avb_slot_verify_data_free(slot_data); +} + TEST_F(AvbSlotVerifyTest, RollbackIndexLocationInChainedPartition) { size_t boot_partition_size = 16 * 1024 * 1024; const size_t boot_image_size = 5 * 1024 * 1024; @@ -1202,6 +1363,7 @@ TEST_F(AvbSlotVerifyTest, RollbackIndexLocationInChainedPartition) { " Rollback Index Location: 2\n" " Public key (sha1): " "2597c218aae470a130f61162feaae70afd97f011\n" + " Flags: 0\n" " Kernel Cmdline descriptor:\n" " Flags: 0\n" " Kernel Cmdline: 'cmdline2 in vbmeta'\n", @@ -1331,6 +1493,7 @@ TEST_F(AvbSlotVerifyTest, HashDescriptorInOtherVBMetaPartition) { " Rollback Index Location: 1\n" " Public key (sha1): " "2597c218aae470a130f61162feaae70afd97f011\n" + " Flags: 0\n" " Kernel Cmdline descriptor:\n" " Flags: 0\n" " Kernel Cmdline: 'cmdline2 in vbmeta'\n", @@ -1433,7 +1596,7 @@ TEST_F(AvbSlotVerifyTest, HashDescriptorInOtherVBMetaPartition) { EXPECT_EQ( "cmdline2 in hash footer cmdline2 in vbmeta " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 " "androidboot.vbmeta.digest=" @@ -1734,6 +1897,7 @@ TEST_F(AvbSlotVerifyTest, ChainedPartitionNoSlots) { " Rollback Index Location: 1\n" " Public key (sha1): " "2597c218aae470a130f61162feaae70afd97f011\n" + " Flags: 0\n" " Kernel Cmdline descriptor:\n" " Flags: 0\n" " Kernel Cmdline: 'cmdline2 in vbmeta'\n", @@ -1779,7 +1943,7 @@ TEST_F(AvbSlotVerifyTest, ChainedPartitionNoSlots) { EXPECT_EQ( "cmdline2 in hash footer cmdline2 in vbmeta " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=4416 " "androidboot.vbmeta.digest=" @@ -2078,7 +2242,7 @@ TEST_F(AvbSlotVerifyTest, NoVBMetaPartitionFlag) { // Note the absence of 'androidboot.vbmeta.device' EXPECT_EQ( "this is=5 from foo=42 and=43 from bar " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=3456 " @@ -2141,7 +2305,7 @@ TEST_F(AvbSlotVerifyTest, PublicKeyMetadata) { EXPECT_NE(nullptr, slot_data); EXPECT_EQ( "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=2688 " "androidboot.vbmeta.digest=" @@ -2251,7 +2415,7 @@ void AvbSlotVerifyTest::CmdlineWithHashtreeVerification( "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 " "should_be_in_both=1 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1536 " "androidboot.vbmeta.digest=" @@ -2265,7 +2429,7 @@ void AvbSlotVerifyTest::CmdlineWithHashtreeVerification( EXPECT_EQ( "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1536 " "androidboot.vbmeta.digest=" @@ -2399,6 +2563,7 @@ void AvbSlotVerifyTest::CmdlineWithChainedHashtreeVerification( " Rollback Index Location: 1\n" " Public key (sha1): " "cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" + " Flags: 0\n" " Kernel Cmdline descriptor:\n" " Flags: 0\n" " Kernel Cmdline: 'should_be_in_both=1'\n", @@ -2432,7 +2597,7 @@ void AvbSlotVerifyTest::CmdlineWithChainedHashtreeVerification( "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 " "should_be_in_both=1 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=3456 " "androidboot.vbmeta.digest=" @@ -2446,7 +2611,7 @@ void AvbSlotVerifyTest::CmdlineWithChainedHashtreeVerification( EXPECT_EQ( "root=PARTUUID=1234-fake-guid-for:system_a should_be_in_both=1 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=3456 " "androidboot.vbmeta.digest=" @@ -2771,6 +2936,7 @@ TEST_F(AvbSlotVerifyTest, NoVBMetaPartition) { " Rollback Index Location: 1\n" " Public key (sha1): " "2597c218aae470a130f61162feaae70afd97f011\n" + " Flags: 0\n" " Kernel Cmdline descriptor:\n" " Flags: 1\n" " Kernel Cmdline: 'dm=\"1 vroot none ro 1,0 32768 verity 1 " @@ -2838,7 +3004,7 @@ TEST_F(AvbSlotVerifyTest, NoVBMetaPartition) { "4096 4096 4096 4096 sha1 c9ffc3bfae5000269a55a56621547fd1fcf819df " "d00df00d 2 restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:boot " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=5312 " "androidboot.vbmeta.digest=" @@ -3009,7 +3175,7 @@ TEST_F(AvbSlotVerifyTest, HashtreeErrorModes) { "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 " "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1664 " @@ -3039,7 +3205,7 @@ TEST_F(AvbSlotVerifyTest, HashtreeErrorModes) { "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 " "restart_on_corruption ignore_zero_blocks\" root=/dev/dm-0 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1664 " @@ -3068,7 +3234,7 @@ TEST_F(AvbSlotVerifyTest, HashtreeErrorModes) { "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 " "ignore_zero_blocks ignore_zero_blocks\" root=/dev/dm-0 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1664 " @@ -3109,7 +3275,7 @@ TEST_F(AvbSlotVerifyTest, HashtreeErrorModes) { "c9ffc3bfae5000269a55a56621547fd1fcf819df d00df00d 2 " "ignore_corruption ignore_zero_blocks\" root=/dev/dm-0 " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1664 " @@ -3148,7 +3314,7 @@ TEST_F(AvbSlotVerifyTest, HashtreeErrorModes) { EXPECT_EQ( "root=PARTUUID=1234-fake-guid-for:system " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1664 " @@ -3340,7 +3506,7 @@ TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic) { Verify(true /* expect_success */); EXPECT_EQ( "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1280 " @@ -3545,7 +3711,7 @@ TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_Hashtree_Sha1) { // Note: Here appear the bytes used in write_persistent_value above. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1408 " @@ -3572,7 +3738,7 @@ TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_Hashtree_Sha256) { // Note: Here appear the bytes used in write_persistent_value above. "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1408 " @@ -3603,7 +3769,7 @@ TEST_F(AvbSlotVerifyTestWithPersistentDigest, Basic_Hashtree_Sha512) { "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa " "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 " "androidboot.vbmeta.size=1408 " @@ -3911,7 +4077,7 @@ TEST_F(AvbSlotVerifyTest, NoSystemPartition) { EXPECT_NE(nullptr, slot_data); EXPECT_EQ( "androidboot.vbmeta.device=PARTUUID=1234-fake-guid-for:vbmeta_a " - "androidboot.vbmeta.avb_version=1.2 " + "androidboot.vbmeta.avb_version=1.3 " "androidboot.vbmeta.device_state=locked " "androidboot.vbmeta.hash_alg=sha256 androidboot.vbmeta.size=1152 " "androidboot.vbmeta.digest=" diff --git a/test/avb_sysdeps_posix_testing.cc b/test/avb_sysdeps_posix_testing.cc index 0a6ac13..6b1f210 100644 --- a/test/avb_sysdeps_posix_testing.cc +++ b/test/avb_sysdeps_posix_testing.cc @@ -77,6 +77,13 @@ void avb_printv(const char* message, ...) { va_end(ap); } +void avb_printf(const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + typedef struct { size_t size; base::debug::StackTrace stack_trace; diff --git a/test/avbtool_unittest.cc b/test/avbtool_unittest.cc index 0a226a8..96e68e2 100644 --- a/test/avbtool_unittest.cc +++ b/test/avbtool_unittest.cc @@ -656,6 +656,8 @@ void AvbToolTest::AddHashFooterTest(bool sparse_image) { EXPECT_EQ(static_cast<size_t>(erased_footer_file_size), rootfs_size); // Check that --do_not_append_vbmeta_image works as intended. + // In this case we don't modify the input image so it should work read-only. + EXPECT_COMMAND(0, "chmod a-w %s", rootfs_path.value().c_str()); EXPECT_COMMAND(0, "./avbtool.py add_hash_footer --salt d00df00d " "--hash_algorithm sha256 --image %s " @@ -2311,6 +2313,7 @@ TEST_F(AvbToolTest, CalculateKernelCmdlineChainedAndWithFlags) { " Rollback Index Location: 1\n" " Public key (sha1): " "cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" + " Flags: 0\n" " Kernel Cmdline descriptor:\n" " Flags: 0\n" " Kernel Cmdline: 'foo bar baz'\n" @@ -2478,7 +2481,91 @@ TEST_F(AvbToolTest, ChainedPartition) { " Partition Name: system\n" " Rollback Index Location: 1\n" " Public key (sha1): " - "cdbb77177f731920bbe0a0f94f84d9038ae0617d\n", + "cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" + " Flags: 0\n", + InfoImage(vbmeta_path)); + + // Now check the VBMeta image. + std::string image_data; + ASSERT_TRUE(base::ReadFileToString(vbmeta_path, &image_data)); + + const uint8_t* vbmeta_data = + reinterpret_cast<const uint8_t*>(image_data.data()); + const size_t vbmeta_size = image_data.length(); + EXPECT_EQ(AVB_VBMETA_VERIFY_RESULT_OK, + avb_vbmeta_image_verify(vbmeta_data, vbmeta_size, NULL, NULL)); + + // Collect all descriptors. + std::vector<const AvbDescriptor*> descriptors; + avb_descriptor_foreach( + vbmeta_data, vbmeta_size, collect_descriptors, &descriptors); + + // We should have one descriptor - check it. + EXPECT_EQ(1UL, descriptors.size()); + + std::string pk_data; + ASSERT_TRUE(base::ReadFileToString(pk_path, &pk_data)); + + AvbChainPartitionDescriptor d; + EXPECT_EQ(AVB_DESCRIPTOR_TAG_CHAIN_PARTITION, + avb_be64toh(descriptors[0]->tag)); + EXPECT_NE( + 0, + avb_chain_partition_descriptor_validate_and_byteswap( + reinterpret_cast<const AvbChainPartitionDescriptor*>(descriptors[0]), + &d)); + const uint8_t* desc_end = reinterpret_cast<const uint8_t*>(descriptors[0]) + + sizeof(AvbChainPartitionDescriptor); + uint64_t o = 0; + EXPECT_EQ("system", + std::string(reinterpret_cast<const char*>(desc_end + o), + d.partition_name_len)); + o += d.partition_name_len; + EXPECT_EQ(pk_data, + std::string(reinterpret_cast<const char*>(descriptors[0]) + + sizeof(AvbChainPartitionDescriptor) + o, + d.public_key_len)); +} + +TEST_F(AvbToolTest, ChainedPartitionNoAB) { + base::FilePath vbmeta_path = testdir_.Append("vbmeta_cp.bin"); + + base::FilePath pk_path = testdir_.Append("testkey_rsa2048.avbpubkey"); + + EXPECT_COMMAND( + 0, + "./avbtool.py extract_public_key --key test/data/testkey_rsa2048.pem" + " --output %s", + pk_path.value().c_str()); + + EXPECT_COMMAND( + 0, + "./avbtool.py make_vbmeta_image " + "--output %s " + "--chain_partition_do_not_use_ab system:1:%s " + "--algorithm SHA256_RSA2048 --key test/data/testkey_rsa2048.pem " + "--internal_release_string \"\"", + vbmeta_path.value().c_str(), + pk_path.value().c_str()); + + ASSERT_EQ( + "Minimum libavb version: 1.3\n" + "Header Block: 256 bytes\n" + "Authentication Block: 320 bytes\n" + "Auxiliary Block: 1152 bytes\n" + "Public key (sha1): cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" + "Algorithm: SHA256_RSA2048\n" + "Rollback Index: 0\n" + "Flags: 0\n" + "Rollback Index Location: 0\n" + "Release String: ''\n" + "Descriptors:\n" + " Chain Partition descriptor:\n" + " Partition Name: system\n" + " Rollback Index Location: 1\n" + " Public key (sha1): " + "cdbb77177f731920bbe0a0f94f84d9038ae0617d\n" + " Flags: 1\n", InfoImage(vbmeta_path)); // Now check the VBMeta image. @@ -2587,7 +2674,7 @@ TEST_F(AvbToolTest, AppendVBMetaImage) { "Rollback Index: 0\n" "Flags: 0\n" "Rollback Index Location: 0\n" - "Release String: 'avbtool 1.2.0 '\n" + "Release String: 'avbtool 1.3.0 '\n" "Descriptors:\n" " Kernel Cmdline descriptor:\n" " Flags: 0\n" @@ -3366,131 +3453,138 @@ TEST_F(AvbToolTest_PrintRequiredVersion, Vbmeta_1_2) { PrintWithMakeVbmetaImage(2); } -TEST_F(AvbToolTest, MakeAtxPikCertificate) { +TEST_F(AvbToolTest, MakeCertPikCertificate) { base::FilePath subject_path = testdir_.Append("tmp_subject"); ASSERT_TRUE(base::WriteFile(subject_path, "fake PIK subject", 16)); base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem"); EXPECT_COMMAND( 0, - "openssl pkey -pubout -in test/data/testkey_atx_pik.pem -out %s", + "openssl pkey -pubout -in test/data/testkey_cert_pik.pem -out %s", pubkey_path.value().c_str()); base::FilePath output_path = testdir_.Append("tmp_certificate.bin"); EXPECT_COMMAND(0, - "./avbtool.py make_atx_certificate" + "./avbtool.py make_certificate" " --subject %s" " --subject_key %s" " --subject_key_version 42" " --subject_is_intermediate_authority" - " --authority_key test/data/testkey_atx_prk.pem" + " --authority_key test/data/testkey_cert_prk.pem" " --output %s", subject_path.value().c_str(), pubkey_path.value().c_str(), output_path.value().c_str()); EXPECT_COMMAND(0, - "diff test/data/atx_pik_certificate.bin %s", + "diff test/data/cert_pik_certificate.bin %s", output_path.value().c_str()); } -TEST_F(AvbToolTest, MakeAtxPskCertificate) { +TEST_F(AvbToolTest, MakeCertPskCertificate) { base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem"); EXPECT_COMMAND( 0, - "openssl pkey -pubout -in test/data/testkey_atx_psk.pem -out %s", + "openssl pkey -pubout -in test/data/testkey_cert_psk.pem -out %s", pubkey_path.value().c_str()); base::FilePath output_path = testdir_.Append("tmp_certificate.bin"); EXPECT_COMMAND(0, - "./avbtool.py make_atx_certificate" - " --subject test/data/atx_product_id.bin" + "./avbtool.py make_certificate" + " --subject test/data/cert_product_id.bin" " --subject_key %s" " --subject_key_version 42" - " --authority_key test/data/testkey_atx_pik.pem" + " --authority_key test/data/testkey_cert_pik.pem" " --output %s", pubkey_path.value().c_str(), output_path.value().c_str()); EXPECT_COMMAND(0, - "diff test/data/atx_psk_certificate.bin %s", + "diff test/data/cert_psk_certificate.bin %s", output_path.value().c_str()); } -TEST_F(AvbToolTest, MakeAtxPukCertificate) { +TEST_F(AvbToolTest, MakeCertPukCertificate) { base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem"); EXPECT_COMMAND( 0, - "openssl pkey -pubout -in test/data/testkey_atx_puk.pem -out %s", + "openssl pkey -pubout -in test/data/testkey_cert_puk.pem -out %s", pubkey_path.value().c_str()); base::FilePath output_path = testdir_.Append("tmp_certificate.bin"); - EXPECT_COMMAND(0, - "./avbtool.py make_atx_certificate" - " --subject test/data/atx_product_id.bin" - " --subject_key %s" - " --subject_key_version 42" - " --usage com.google.android.things.vboot.unlock" - " --authority_key test/data/testkey_atx_pik.pem" - " --output %s", - pubkey_path.value().c_str(), - output_path.value().c_str()); - EXPECT_COMMAND(0, - "diff test/data/atx_puk_certificate.bin %s", - output_path.value().c_str()); + // Test with both legacy manual unlock --usage as well as --usage_for_unlock. + std::string usage_args[] = {"--usage com.google.android.things.vboot.unlock", + "--usage_for_unlock"}; + for (const auto& usage : usage_args) { + EXPECT_COMMAND(0, + "./avbtool.py make_certificate" + " --subject test/data/cert_product_id.bin" + " --subject_key %s" + " --subject_key_version 42" + " %s" + " --authority_key test/data/testkey_cert_pik.pem" + " --output %s", + pubkey_path.value().c_str(), + usage.c_str(), + output_path.value().c_str()); + + EXPECT_COMMAND(0, + "diff test/data/cert_puk_certificate.bin %s", + output_path.value().c_str()); + } } -TEST_F(AvbToolTest, MakeAtxPermanentAttributes) { +TEST_F(AvbToolTest, MakeCertPermanentAttributes) { base::FilePath pubkey_path = testdir_.Append("tmp_pubkey.pem"); EXPECT_COMMAND( 0, - "openssl pkey -pubout -in test/data/testkey_atx_prk.pem -out %s", + "openssl pkey -pubout -in test/data/testkey_cert_prk.pem -out %s", pubkey_path.value().c_str()); base::FilePath output_path = testdir_.Append("tmp_attributes.bin"); EXPECT_COMMAND(0, - "./avbtool.py make_atx_permanent_attributes" + "./avbtool.py make_cert_permanent_attributes" " --root_authority_key %s" - " --product_id test/data/atx_product_id.bin" + " --product_id test/data/cert_product_id.bin" " --output %s", pubkey_path.value().c_str(), output_path.value().c_str()); EXPECT_COMMAND(0, - "diff test/data/atx_permanent_attributes.bin %s", + "diff test/data/cert_permanent_attributes.bin %s", output_path.value().c_str()); } -TEST_F(AvbToolTest, MakeAtxMetadata) { +TEST_F(AvbToolTest, MakeCertMetadata) { base::FilePath output_path = testdir_.Append("tmp_metadata.bin"); EXPECT_COMMAND( 0, - "./avbtool.py make_atx_metadata" - " --intermediate_key_certificate test/data/atx_pik_certificate.bin" - " --product_key_certificate test/data/atx_psk_certificate.bin" + "./avbtool.py make_cert_metadata" + " --intermediate_key_certificate test/data/cert_pik_certificate.bin" + " --product_key_certificate test/data/cert_psk_certificate.bin" " --output %s", output_path.value().c_str()); EXPECT_COMMAND( - 0, "diff test/data/atx_metadata.bin %s", output_path.value().c_str()); + 0, "diff test/data/cert_metadata.bin %s", output_path.value().c_str()); } -TEST_F(AvbToolTest, MakeAtxUnlockCredential) { +TEST_F(AvbToolTest, MakeCertUnlockCredential) { base::FilePath output_path = testdir_.Append("tmp_credential.bin"); EXPECT_COMMAND( 0, - "./avbtool.py make_atx_unlock_credential" - " --intermediate_key_certificate test/data/atx_pik_certificate.bin" - " --unlock_key_certificate test/data/atx_puk_certificate.bin" - " --challenge test/data/atx_unlock_challenge.bin" - " --unlock_key test/data/testkey_atx_puk.pem" + "./avbtool.py make_cert_unlock_credential" + " --intermediate_key_certificate test/data/cert_pik_certificate.bin" + " --unlock_key_certificate test/data/cert_puk_certificate.bin" + " --challenge test/data/cert_unlock_challenge.bin" + " --unlock_key test/data/testkey_cert_puk.pem" " --output %s", output_path.value().c_str()); EXPECT_COMMAND(0, - "diff test/data/atx_unlock_credential.bin %s", + "diff test/data/cert_unlock_credential.bin %s", output_path.value().c_str()); } diff --git a/test/data/atx_metadata.bin b/test/data/cert_metadata.bin Binary files differindex 58666a1..58666a1 100644 --- a/test/data/atx_metadata.bin +++ b/test/data/cert_metadata.bin diff --git a/test/data/atx_permanent_attributes.bin b/test/data/cert_permanent_attributes.bin Binary files differindex 51ab594..51ab594 100644 --- a/test/data/atx_permanent_attributes.bin +++ b/test/data/cert_permanent_attributes.bin diff --git a/test/data/atx_pik_certificate.bin b/test/data/cert_pik_certificate.bin Binary files differindex 6e01e6c..6e01e6c 100644 --- a/test/data/atx_pik_certificate.bin +++ b/test/data/cert_pik_certificate.bin diff --git a/test/data/atx_product_id.bin b/test/data/cert_product_id.bin Binary files differindex 01d633b..01d633b 100644 --- a/test/data/atx_product_id.bin +++ b/test/data/cert_product_id.bin diff --git a/test/data/atx_psk_certificate.bin b/test/data/cert_psk_certificate.bin Binary files differindex c0cfeda..c0cfeda 100644 --- a/test/data/atx_psk_certificate.bin +++ b/test/data/cert_psk_certificate.bin diff --git a/test/data/atx_puk_certificate.bin b/test/data/cert_puk_certificate.bin Binary files differindex 0e02cfe..0e02cfe 100644 --- a/test/data/atx_puk_certificate.bin +++ b/test/data/cert_puk_certificate.bin diff --git a/test/data/atx_unlock_challenge.bin b/test/data/cert_unlock_challenge.bin index 23494f9..23494f9 100644 --- a/test/data/atx_unlock_challenge.bin +++ b/test/data/cert_unlock_challenge.bin diff --git a/test/data/atx_unlock_credential.bin b/test/data/cert_unlock_credential.bin Binary files differindex b5b46d9..b5b46d9 100644 --- a/test/data/atx_unlock_credential.bin +++ b/test/data/cert_unlock_credential.bin diff --git a/test/data/testkey_atx_pik.pem b/test/data/testkey_cert_pik.pem index 5f34cb9..5f34cb9 100644 --- a/test/data/testkey_atx_pik.pem +++ b/test/data/testkey_cert_pik.pem diff --git a/test/data/testkey_atx_prk.pem b/test/data/testkey_cert_prk.pem index 17a8cce..17a8cce 100644 --- a/test/data/testkey_atx_prk.pem +++ b/test/data/testkey_cert_prk.pem diff --git a/test/data/testkey_atx_psk.pem b/test/data/testkey_cert_psk.pem index 71bfebf..71bfebf 100644 --- a/test/data/testkey_atx_psk.pem +++ b/test/data/testkey_cert_psk.pem diff --git a/test/data/testkey_atx_puk.pem b/test/data/testkey_cert_puk.pem index c8053be..c8053be 100644 --- a/test/data/testkey_atx_puk.pem +++ b/test/data/testkey_cert_puk.pem diff --git a/test/fake_avb_ops.cc b/test/fake_avb_ops.cc index 0f4db5a..517770e 100644 --- a/test/fake_avb_ops.cc +++ b/test/fake_avb_ops.cc @@ -398,7 +398,7 @@ AvbIOResult FakeAvbOps::write_persistent_value(const char* name, } AvbIOResult FakeAvbOps::read_permanent_attributes( - AvbAtxPermanentAttributes* attributes) { + AvbCertPermanentAttributes* attributes) { *attributes = permanent_attributes_; return AVB_IO_RESULT_OK; } @@ -407,7 +407,7 @@ AvbIOResult FakeAvbOps::read_permanent_attributes_hash( uint8_t hash[AVB_SHA256_DIGEST_SIZE]) { if (permanent_attributes_hash_.empty()) { SHA256(reinterpret_cast<const unsigned char*>(&permanent_attributes_), - sizeof(AvbAtxPermanentAttributes), + sizeof(AvbCertPermanentAttributes), hash); return AVB_IO_RESULT_OK; } @@ -560,31 +560,31 @@ static AvbIOResult my_ops_write_persistent_value(AvbOps* ops, } static AvbIOResult my_ops_read_permanent_attributes( - AvbAtxOps* atx_ops, AvbAtxPermanentAttributes* attributes) { - return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops) + AvbCertOps* cert_ops, AvbCertPermanentAttributes* attributes) { + return FakeAvbOps::GetInstanceFromAvbOps(cert_ops->ops) ->delegate() ->read_permanent_attributes(attributes); } static AvbIOResult my_ops_read_permanent_attributes_hash( - AvbAtxOps* atx_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) { - return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops) + AvbCertOps* cert_ops, uint8_t hash[AVB_SHA256_DIGEST_SIZE]) { + return FakeAvbOps::GetInstanceFromAvbOps(cert_ops->ops) ->delegate() ->read_permanent_attributes_hash(hash); } -static void my_ops_set_key_version(AvbAtxOps* atx_ops, +static void my_ops_set_key_version(AvbCertOps* cert_ops, size_t rollback_index_location, uint64_t key_version) { - return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops) + return FakeAvbOps::GetInstanceFromAvbOps(cert_ops->ops) ->delegate() ->set_key_version(rollback_index_location, key_version); } -static AvbIOResult my_ops_get_random(AvbAtxOps* atx_ops, +static AvbIOResult my_ops_get_random(AvbCertOps* cert_ops, size_t num_bytes, uint8_t* output) { - return FakeAvbOps::GetInstanceFromAvbOps(atx_ops->ops) + return FakeAvbOps::GetInstanceFromAvbOps(cert_ops->ops) ->delegate() ->get_random(num_bytes, output); } @@ -592,7 +592,7 @@ static AvbIOResult my_ops_get_random(AvbAtxOps* atx_ops, FakeAvbOps::FakeAvbOps() { memset(&avb_ops_, 0, sizeof(avb_ops_)); avb_ops_.ab_ops = &avb_ab_ops_; - avb_ops_.atx_ops = &avb_atx_ops_; + avb_ops_.cert_ops = &avb_cert_ops_; avb_ops_.user_data = this; avb_ops_.read_from_partition = my_ops_read_from_partition; avb_ops_.write_to_partition = my_ops_write_to_partition; @@ -612,12 +612,12 @@ FakeAvbOps::FakeAvbOps() { avb_ab_ops_.read_ab_metadata = avb_ab_data_read; avb_ab_ops_.write_ab_metadata = avb_ab_data_write; - avb_atx_ops_.ops = &avb_ops_; - avb_atx_ops_.read_permanent_attributes = my_ops_read_permanent_attributes; - avb_atx_ops_.read_permanent_attributes_hash = + avb_cert_ops_.ops = &avb_ops_; + avb_cert_ops_.read_permanent_attributes = my_ops_read_permanent_attributes; + avb_cert_ops_.read_permanent_attributes_hash = my_ops_read_permanent_attributes_hash; - avb_atx_ops_.set_key_version = my_ops_set_key_version; - avb_atx_ops_.get_random = my_ops_get_random; + avb_cert_ops_.set_key_version = my_ops_set_key_version; + avb_cert_ops_.get_random = my_ops_get_random; delegate_ = this; } diff --git a/test/fake_avb_ops.h b/test/fake_avb_ops.h index 5dea5bd..8d9e49b 100644 --- a/test/fake_avb_ops.h +++ b/test/fake_avb_ops.h @@ -26,13 +26,13 @@ #define FAKE_AVB_OPS_H_ #include <base/files/file_util.h> +#include <libavb_ab/libavb_ab.h> +#include <libavb_cert/libavb_cert.h> + #include <map> #include <set> #include <string> -#include <libavb_ab/libavb_ab.h> -#include <libavb_atx/libavb_atx.h> - namespace avb { // A delegate interface for ops callbacks. This allows tests to override default @@ -106,7 +106,7 @@ class FakeAvbOpsDelegate { uint32_t* out_rollback_index_location) = 0; virtual AvbIOResult read_permanent_attributes( - AvbAtxPermanentAttributes* attributes) = 0; + AvbCertPermanentAttributes* attributes) = 0; virtual AvbIOResult read_permanent_attributes_hash( uint8_t hash[AVB_SHA256_DIGEST_SIZE]) = 0; @@ -139,8 +139,8 @@ class FakeAvbOps : public FakeAvbOpsDelegate { return &avb_ab_ops_; } - AvbAtxOps* avb_atx_ops() { - return &avb_atx_ops_; + AvbCertOps* avb_cert_ops() { + return &avb_cert_ops_; } FakeAvbOpsDelegate* delegate() { @@ -192,7 +192,7 @@ class FakeAvbOps : public FakeAvbOpsDelegate { stored_is_device_unlocked_ = stored_is_device_unlocked; } - void set_permanent_attributes(const AvbAtxPermanentAttributes& attributes) { + void set_permanent_attributes(const AvbCertPermanentAttributes& attributes) { permanent_attributes_ = attributes; } @@ -283,7 +283,7 @@ class FakeAvbOps : public FakeAvbOpsDelegate { uint32_t* out_rollback_index_location) override; AvbIOResult read_permanent_attributes( - AvbAtxPermanentAttributes* attributes) override; + AvbCertPermanentAttributes* attributes) override; AvbIOResult read_permanent_attributes_hash( uint8_t hash[AVB_SHA256_DIGEST_SIZE]) override; @@ -296,7 +296,7 @@ class FakeAvbOps : public FakeAvbOpsDelegate { private: AvbOps avb_ops_; AvbABOps avb_ab_ops_; - AvbAtxOps avb_atx_ops_; + AvbCertOps avb_cert_ops_; FakeAvbOpsDelegate* delegate_; @@ -314,7 +314,7 @@ class FakeAvbOps : public FakeAvbOpsDelegate { bool stored_is_device_unlocked_; - AvbAtxPermanentAttributes permanent_attributes_; + AvbCertPermanentAttributes permanent_attributes_; std::string permanent_attributes_hash_; std::set<std::string> partition_names_read_from_; @@ -434,7 +434,7 @@ class FakeAvbOpsDelegateWithDefaults : public FakeAvbOpsDelegate { } AvbIOResult read_permanent_attributes( - AvbAtxPermanentAttributes* attributes) override { + AvbCertPermanentAttributes* attributes) override { return ops_.read_permanent_attributes(attributes); } diff --git a/test/user_code_test.cc b/test/user_code_test.cc index fd851f3..8c449c3 100644 --- a/test/user_code_test.cc +++ b/test/user_code_test.cc @@ -26,4 +26,4 @@ // defining AVB_COMPILATION (which user code must not). #include "libavb/libavb.h" #include "libavb_ab/libavb_ab.h" -#include "libavb_atx/libavb_atx.h" +#include "libavb_cert/libavb_cert.h" diff --git a/tools/at_auth_unlock.py b/tools/at_auth_unlock.py index c7e6869..ee08040 100755 --- a/tools/at_auth_unlock.py +++ b/tools/at_auth_unlock.py @@ -99,7 +99,7 @@ class UnlockCredentials(object): unlock_cert_file, unlock_key_file, source_file=None): - # The certificates are AvbAtxCertificate structs as defined in libavb_atx, + # The certificates are AvbCertCertificate structs as defined in libavb_cert, # not an X.509 certificate. Do a basic length sanity check when reading # them. EXPECTED_CERTIFICATE_SIZE = 1620 @@ -199,10 +199,10 @@ class UnlockCredentials(object): class UnlockChallenge(object): - """Helper class for parsing the AvbAtxUnlockChallenge struct returned from 'fastboot oem at-get-vboot-unlock-challenge'. + """Helper class for parsing the AvbCertUnlockChallenge struct returned from 'fastboot oem at-get-vboot-unlock-challenge'. The file provided to the constructor should be the full 52-byte - AvbAtxUnlockChallenge struct, not just the challenge itself. + AvbCertUnlockChallenge struct, not just the challenge itself. """ def __init__(self, challenge_file): @@ -230,8 +230,8 @@ class UnlockChallenge(object): return self._challenge_data -def GetAtxCertificateSubject(cert): - """Parses and returns the subject field from the given AvbAtxCertificate struct.""" +def GetCertCertificateSubject(cert): + """Parses and returns the subject field from the given AvbCertCertificate struct.""" CERT_SUBJECT_OFFSET = 4 + 1032 # Format version and public key come before subject CERT_SUBJECT_LENGTH = 32 return cert[CERT_SUBJECT_OFFSET:CERT_SUBJECT_OFFSET + CERT_SUBJECT_LENGTH] @@ -251,12 +251,12 @@ def SelectMatchingUnlockCredential(all_creds, challenge): 'fastboot oem at-get-vboot-unlock-challenge'. """ for creds in all_creds: - if GetAtxCertificateSubject(creds.unlock_cert) == challenge.product_id_hash: + if GetCertCertificateSubject(creds.unlock_cert) == challenge.product_id_hash: return creds -def MakeAtxUnlockCredential(creds, challenge, out_file): - """Simple reimplementation of 'avbtool make_atx_unlock_credential'. +def MakeCertUnlockCredential(creds, challenge, out_file): + """Simple reimplementation of 'avbtool make_cert_unlock_credential'. Generates an Android Things authenticated unlock credential to authorize unlocking AVB on a device. @@ -271,7 +271,7 @@ def MakeAtxUnlockCredential(creds, challenge, out_file): certificate, and PUK private key. challenge: UnlockChallenge object created from challenge obtained via 'fastboot oem at-get-vboot-unlock-challenge'. - out_file: Output filename to write the AvbAtxUnlockCredential struct to. + out_file: Output filename to write the AvbCertUnlockCredential struct to. Raises: ValueError: If challenge has wrong length. @@ -333,7 +333,7 @@ def AuthenticatedUnlock(all_creds, serial=None, verbose=False): if selected_cred.source_file: print('Found matching unlock credentials: {}'.format( selected_cred.source_file)) - MakeAtxUnlockCredential(selected_cred, challenge, credential_file) + MakeCertUnlockCredential(selected_cred, challenge, credential_file) fastboot_cmd(['stage', credential_file]) fastboot_cmd(['oem', 'at-unlock-vboot']) diff --git a/tools/transparency/verify/internal/checkpoint/checkpoint.go b/tools/transparency/verify/internal/checkpoint/checkpoint.go index 600707f..dbba338 100644 --- a/tools/transparency/verify/internal/checkpoint/checkpoint.go +++ b/tools/transparency/verify/internal/checkpoint/checkpoint.go @@ -6,10 +6,9 @@ // // When a commitment needs to be sent to other processes (such as a witness or // other log clients), it is put in the form of a checkpoint, which also -// includes an "ecosystem identifier". The "ecosystem identifier" defines how -// to parse the checkpoint data. This package deals only with the DEFAULT -// ecosystem, which has only the information from Root and no additional data. -// Support for other ecosystems will be added as needed. +// includes an "origin" string. The origin should is a unique identifier for +// the log identity which issues the checkpoint. This package deals only with +// the origin for the Pixel Binary Transparency Log. // // This checkpoint is signed in a note format (golang.org/x/mod/sumdb/note) // before sending out. An unsigned checkpoint is not a valid commitment and @@ -39,8 +38,8 @@ import ( ) const ( - // defaultEcosystemID identifies a checkpoint in the DEFAULT ecosystem. - defaultEcosystemID = "DEFAULT\n" + // originID identifies a checkpoint for the Pixel Binary Transparency Log. + originID = "developers.google.com/android/binary_transparency/0\n" ) type verifier interface { @@ -99,7 +98,7 @@ func NewVerifier(pemKey []byte, name string) (EcdsaVerifier, error) { }, nil } -// Root contains the checkpoint data for a DEFAULT ecosystem checkpoint. +// Root contains the checkpoint data. type Root struct { // Size is the number of entries in the log at this point. Size uint64 @@ -108,15 +107,15 @@ type Root struct { } func parseCheckpoint(ckpt string) (Root, error) { - if !strings.HasPrefix(ckpt, defaultEcosystemID) { - return Root{}, errors.New("invalid checkpoint - unknown ecosystem, must be DEFAULT") + if !strings.HasPrefix(ckpt, originID) { + return Root{}, errors.New(fmt.Sprintf("invalid checkpoint - unknown origin, must be %s", originID)) } - // Strip the ecosystem ID and parse the rest of the checkpoint. - body := ckpt[len(defaultEcosystemID):] + // Strip the origin ID and parse the rest of the checkpoint. + body := ckpt[len(originID):] // body must contain exactly 2 lines, size and the root hash. l := strings.SplitN(body, "\n", 3) if len(l) != 3 || len(l[2]) != 0 { - return Root{}, errors.New("invalid checkpoint - bad format: must have ecosystem id, size and root hash each followed by newline") + return Root{}, errors.New("invalid checkpoint - bad format: must have origin id, size and root hash each followed by newline") } size, err := strconv.ParseUint(l[0], 10, 64) if err != nil { @@ -156,11 +155,12 @@ func getSignedCheckpoint(logURL string) ([]byte, error) { // Data at `logURL` is the checkpoint and must be in the note format // (golang.org/x/mod/sumdb/note). // -// The checkpoint must be in the DEFAULT ecosystem. +// The checkpoint must be for the Pixel Binary Transparency Log origin. // // Returns error if the signature fails to verify or if the checkpoint // does not conform to the following format: -// []byte("[ecosystem]\n[size]\n[hash]"). +// +// []byte("[origin]\n[size]\n[hash]"). func FromURL(logURL string, v verifier) (Root, error) { b, err := getSignedCheckpoint(logURL) if err != nil { diff --git a/tools/transparency/verify/internal/checkpoint/checkpoint_test.go b/tools/transparency/verify/internal/checkpoint/checkpoint_test.go index 1c47e82..8f6ec4b 100644 --- a/tools/transparency/verify/internal/checkpoint/checkpoint_test.go +++ b/tools/transparency/verify/internal/checkpoint/checkpoint_test.go @@ -17,33 +17,33 @@ func TestInvalidCheckpointFormat(t *testing.T) { wantErr bool }{ { - desc: "unknown ecosystem", + desc: "unknown origin", m: "UNKNOWN\n1\nbananas\n", wantErr: true, }, { desc: "bad size", - m: "DEFAULT\n-1\nbananas\n", + m: "developers.google.com/android/binary_transparency/0\n-1\nbananas\n", wantErr: true, }, { desc: "not enough newlines", - m: "DEFAULT\n1\n", + m: "developers.google.com/android/binary_transparency/0\n1\n", wantErr: true, }, { desc: "non-numeric size", - m: "DEFAULT\nbananas\ndGhlIHZpZXcgZnJvbSB0aGUgdHJlZSB0b3BzIGlzIGdyZWF0IQ==\n", + m: "developers.google.com/android/binary_transparency/0\nbananas\ndGhlIHZpZXcgZnJvbSB0aGUgdHJlZSB0b3BzIGlzIGdyZWF0IQ==\n", wantErr: true, }, { desc: "too many newlines", - m: "DEFAULT\n1\n\n\n\n", + m: "developers.google.com/android/binary_transparency/0\n1\n\n\n\n", wantErr: true, }, { desc: "does not end with newline", - m: "DEFAULT\n1\ngarbage", + m: "developers.google.com/android/binary_transparency/0\n1\ngarbage", wantErr: true, }, { |