aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:34:23 +0000
committerAndroid Build Coastguard Worker <android-build-coastguard-worker@google.com>2024-05-10 15:34:23 +0000
commit1b9ced4dfea56ae3621a1b970089318b80590364 (patch)
treeb6725d91c88dd8c52e9efa011d743a62d1c093dd
parent467c0d3ad610e167155572d538d9ec10282ec1f6 (diff)
parent605ae13dda96ebdfb651956a9c50f8ae3ef38eb8 (diff)
downloadavb-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.bp88
-rw-r--r--README.md123
-rw-r--r--TEST_MAPPING2
-rwxr-xr-xavbtool.py300
-rw-r--r--boot_control/boot_control_avb.c2
-rw-r--r--examples/cert/README.md6
-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.md6
-rw-r--r--examples/uefi/main.c7
-rw-r--r--examples/uefi/uefi_avb_boot.c3
-rw-r--r--libavb/avb_chain_partition_descriptor.c1
-rw-r--r--libavb/avb_chain_partition_descriptor.h16
-rw-r--r--libavb/avb_ops.h10
-rw-r--r--libavb/avb_slot_verify.c236
-rw-r--r--libavb/avb_sysdeps.h9
-rw-r--r--libavb/avb_sysdeps_posix.c25
-rw-r--r--libavb/avb_util.h129
-rw-r--r--libavb/avb_version.h2
-rw-r--r--libavb_ab/avb_ab_flow.c26
-rw-r--r--libavb_atx/avb_atx_types.h96
-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.h96
-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.cpp12
-rw-r--r--libavb_user/avb_user_verification.c32
-rw-r--r--libavb_user/avb_user_verity.c32
-rw-r--r--rust/Android.bp520
-rw-r--r--rust/OWNERS2
-rw-r--r--rust/TEST_MAPPING19
-rw-r--r--rust/bindgen/avb.h20
-rw-r--r--rust/src/cert.rs399
-rw-r--r--rust/src/descriptor/chain.rs117
-rw-r--r--rust/src/descriptor/commandline.rs102
-rw-r--r--rust/src/descriptor/hash.rs131
-rw-r--r--rust/src/descriptor/hashtree.rs157
-rw-r--r--rust/src/descriptor/mod.rs271
-rw-r--r--rust/src/descriptor/property.rs108
-rw-r--r--rust/src/descriptor/util.rs178
-rw-r--r--rust/src/error.rs380
-rw-r--r--rust/src/lib.rs53
-rw-r--r--rust/src/ops.rs1343
-rw-r--r--rust/src/verify.rs460
-rw-r--r--rust/testdata/chain_partition_descriptor.binbin0 -> 2160 bytes
-rw-r--r--rust/testdata/hash_descriptor.binbin0 -> 176 bytes
-rw-r--r--rust/testdata/hashtree_descriptor.binbin0 -> 240 bytes
-rw-r--r--rust/testdata/kernel_commandline_descriptor.binbin0 -> 64 bytes
-rw-r--r--rust/testdata/property_descriptor.binbin0 -> 64 bytes
-rw-r--r--rust/tests/cert_tests.rs289
-rw-r--r--rust/tests/test_data.rs73
-rw-r--r--rust/tests/test_ops.rs416
-rw-r--r--rust/tests/tests.rs53
-rw-r--r--rust/tests/verify_tests.rs796
-rw-r--r--test/Android.bp42
-rw-r--r--test/at_auth_unlock_unittest.py24
-rwxr-xr-xtest/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.cc214
-rw-r--r--test/avb_sysdeps_posix_testing.cc7
-rw-r--r--test/avbtool_unittest.cc184
-rw-r--r--test/data/cert_metadata.bin (renamed from test/data/atx_metadata.bin)bin3244 -> 3244 bytes
-rw-r--r--test/data/cert_permanent_attributes.bin (renamed from test/data/atx_permanent_attributes.bin)bin1052 -> 1052 bytes
-rw-r--r--test/data/cert_pik_certificate.bin (renamed from test/data/atx_pik_certificate.bin)bin1620 -> 1620 bytes
-rw-r--r--test/data/cert_product_id.bin (renamed from test/data/atx_product_id.bin)bin16 -> 16 bytes
-rw-r--r--test/data/cert_psk_certificate.bin (renamed from test/data/atx_psk_certificate.bin)bin1620 -> 1620 bytes
-rw-r--r--test/data/cert_puk_certificate.bin (renamed from test/data/atx_puk_certificate.bin)bin1620 -> 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)bin3756 -> 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.cc32
-rw-r--r--test/fake_avb_ops.h22
-rw-r--r--test/user_code_test.cc2
-rwxr-xr-xtools/at_auth_unlock.py20
-rw-r--r--tools/transparency/verify/internal/checkpoint/checkpoint.go28
-rw-r--r--tools/transparency/verify/internal/checkpoint/checkpoint_test.go12
81 files changed, 7466 insertions, 1140 deletions
diff --git a/Android.bp b/Android.bp
index b4660f8..6643a91 100644
--- a/Android.bp
+++ b/Android.bp
@@ -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",
+ ],
}
diff --git a/README.md b/README.md
index 9b51426..032d02e 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/avbtool.py b/avbtool.py
index 67416d4..d923b31 100755
--- a/avbtool.py
+++ b/avbtool.py
@@ -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
new file mode 100644
index 0000000..5331273
--- /dev/null
+++ b/rust/testdata/chain_partition_descriptor.bin
Binary files differ
diff --git a/rust/testdata/hash_descriptor.bin b/rust/testdata/hash_descriptor.bin
new file mode 100644
index 0000000..940732e
--- /dev/null
+++ b/rust/testdata/hash_descriptor.bin
Binary files differ
diff --git a/rust/testdata/hashtree_descriptor.bin b/rust/testdata/hashtree_descriptor.bin
new file mode 100644
index 0000000..cf2b264
--- /dev/null
+++ b/rust/testdata/hashtree_descriptor.bin
Binary files differ
diff --git a/rust/testdata/kernel_commandline_descriptor.bin b/rust/testdata/kernel_commandline_descriptor.bin
new file mode 100644
index 0000000..f6f3fde
--- /dev/null
+++ b/rust/testdata/kernel_commandline_descriptor.bin
Binary files differ
diff --git a/rust/testdata/property_descriptor.bin b/rust/testdata/property_descriptor.bin
new file mode 100644
index 0000000..ea78832
--- /dev/null
+++ b/rust/testdata/property_descriptor.bin
Binary files differ
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
index 58666a1..58666a1 100644
--- a/test/data/atx_metadata.bin
+++ b/test/data/cert_metadata.bin
Binary files differ
diff --git a/test/data/atx_permanent_attributes.bin b/test/data/cert_permanent_attributes.bin
index 51ab594..51ab594 100644
--- a/test/data/atx_permanent_attributes.bin
+++ b/test/data/cert_permanent_attributes.bin
Binary files differ
diff --git a/test/data/atx_pik_certificate.bin b/test/data/cert_pik_certificate.bin
index 6e01e6c..6e01e6c 100644
--- a/test/data/atx_pik_certificate.bin
+++ b/test/data/cert_pik_certificate.bin
Binary files differ
diff --git a/test/data/atx_product_id.bin b/test/data/cert_product_id.bin
index 01d633b..01d633b 100644
--- a/test/data/atx_product_id.bin
+++ b/test/data/cert_product_id.bin
Binary files differ
diff --git a/test/data/atx_psk_certificate.bin b/test/data/cert_psk_certificate.bin
index c0cfeda..c0cfeda 100644
--- a/test/data/atx_psk_certificate.bin
+++ b/test/data/cert_psk_certificate.bin
Binary files differ
diff --git a/test/data/atx_puk_certificate.bin b/test/data/cert_puk_certificate.bin
index 0e02cfe..0e02cfe 100644
--- a/test/data/atx_puk_certificate.bin
+++ b/test/data/cert_puk_certificate.bin
Binary files differ
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
index b5b46d9..b5b46d9 100644
--- a/test/data/atx_unlock_credential.bin
+++ b/test/data/cert_unlock_credential.bin
Binary files differ
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,
},
{