aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Pursell <dpursell@google.com>2024-04-03 16:48:05 +0000
committerGerrit Code Review <noreply-gerritcodereview@google.com>2024-04-03 16:48:05 +0000
commit172666e842431fa1f0e76e8447c8f13cfaef960e (patch)
tree0636b52d238b69d2d5272a98c08b329f21d089fb
parentfa26a9b3b8a1523f1ec1a8a1a7852bdcd91d78fd (diff)
parent79c7971629dd8bb6531f2e3c5325f5e172a9679f (diff)
downloadavb-172666e842431fa1f0e76e8447c8f13cfaef960e.tar.gz
Merge "libavb_rs: ATX documentation and trait" into main
-rw-r--r--rust/bindgen/avb.h1
-rw-r--r--rust/src/atx.rs250
-rw-r--r--rust/src/lib.rs6
-rw-r--r--rust/src/ops.rs6
4 files changed, 262 insertions, 1 deletions
diff --git a/rust/bindgen/avb.h b/rust/bindgen/avb.h
index b3d5385..b9849b6 100644
--- a/rust/bindgen/avb.h
+++ b/rust/bindgen/avb.h
@@ -17,3 +17,4 @@
#pragma once
#include <libavb/libavb.h>
+#include <libavb_atx/libavb_atx.h>
diff --git a/rust/src/atx.rs b/rust/src/atx.rs
new file mode 100644
index 0000000..e9192d2
--- /dev/null
+++ b/rust/src/atx.rs
@@ -0,0 +1,250 @@
+// 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.
+
+//! ATX support.
+//!
+//! ATX 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.
+//!
+//! ATX 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 `AtxOps` trait and forward
+//! `validate_vbmeta_public_key()` and/or `validate_public_key_for_partition()` to the provided
+//! `atx_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, ATX
+//! 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 `atx_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_atx_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 `atx_validate_unlock_credential()` to verify the credential.
+//! ```
+
+use crate::{IoError, IoResult, Ops};
+
+/// ATX permanent attributes.
+pub use avb_bindgen::AvbAtxPermanentAttributes as AtxPermanentAttributes;
+
+/// Authenticated unlock challenge.
+pub use avb_bindgen::AvbAtxUnlockChallenge as AtxUnlockChallenge;
+
+/// Signed authenticated unlock credential.
+pub use avb_bindgen::AvbAtxUnlockCredential as AtxUnlockCredential;
+
+/// 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 ATX APIs, make sure no vbmetas use this location, it must be reserved for the PIK.
+pub const ATX_PIK_VERSION_LOCATION: usize = avb_bindgen::AVB_ATX_PIK_VERSION_LOCATION as usize;
+
+/// Product signing key (PSK) rollback index location.
+///
+/// If using ATX APIs, make sure no vbmetas use this location, it must be reserved for the PSK.
+pub const ATX_PSK_VERSION_LOCATION: usize = avb_bindgen::AVB_ATX_PSK_VERSION_LOCATION as usize;
+
+/// ATX additional callbacks.
+pub trait AtxOps<'a>: Ops<'a> {
+ /// 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 AtxPermanentAttributes,
+ ) -> 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.
+ ///
+ /// ATX 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<()>;
+}
+
+/// ATX-provided 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 ATX 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> {
+/// atx_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 ATX e.g. a device may want to use ATX only for specific partitions.
+///
+/// # Arguments
+/// * `ops`: the `AtxOps` callback implementations.
+/// * `public_key`: the public key.
+/// * `public_key_metadata`: public key metadata.
+///
+/// # Returns
+/// True if the given key is valid, false if it is not, `IoError` on error.
+pub fn atx_validate_vbmeta_public_key(
+ _ops: &mut dyn AtxOps,
+ _public_key: &[u8],
+ _public_key_metadata: Option<&[u8]>,
+) -> IoResult<bool> {
+ // TODO(b/320543206): implement
+ Err(IoError::NotImplemented)
+}
+
+/// 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_atx_unlock_credential`.
+///
+/// # Arguments
+/// * `atx_ops`: the `AtxOps` callback implementations.
+///
+/// # Returns
+/// The challenge to sign with the PUK, or `IoError` on `atx_ops` failure.
+pub fn atx_generate_unlock_challenge(_atx_ops: &mut dyn AtxOps) -> IoResult<AtxUnlockChallenge> {
+ // TODO(b/320543206): implement
+ Err(IoError::NotImplemented)
+}
+
+/// 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
+/// * `atx_ops`: the `AtxOps` callback implementations.
+/// * `credential`: the signed unlock credential to verify.
+///
+/// # Returns
+/// * `Ok(true)` if the credential validated
+/// * `Ok(false)` if it failed validation
+/// * `Err(IoError)` on `atx_ops` failure
+pub fn atx_validate_unlock_credential(
+ _atx_ops: &mut dyn AtxOps,
+ _credential: &AtxUnlockCredential,
+) -> IoResult<bool> {
+ // TODO(b/320543206): implement
+ Err(IoError::NotImplemented)
+}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index c96d4e5..2a49cee 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -26,11 +26,17 @@
// panic_handler and eh_personality conditional on actually building a dylib.
#![cfg_attr(not(any(test, android_dylib)), no_std)]
+mod atx;
mod descriptor;
mod error;
mod ops;
mod verify;
+pub use atx::{
+ atx_generate_unlock_challenge, atx_validate_unlock_credential, atx_validate_vbmeta_public_key,
+ AtxOps, AtxPermanentAttributes, ATX_PIK_VERSION_LOCATION, ATX_PSK_VERSION_LOCATION,
+ SHA256_DIGEST_SIZE,
+};
pub use descriptor::{
ChainPartitionDescriptor, ChainPartitionDescriptorFlags, Descriptor, DescriptorError,
DescriptorResult, HashDescriptor, HashDescriptorFlags, HashtreeDescriptor,
diff --git a/rust/src/ops.rs b/rust/src/ops.rs
index 4b67b0f..cc3a9cf 100644
--- a/rust/src/ops.rs
+++ b/rust/src/ops.rs
@@ -86,6 +86,8 @@ pub trait Ops<'a> {
/// Checks if the given public key is valid for vbmeta image signing.
///
+ /// If using ATX, this should forward to `atx_validate_vbmeta_public_key()`.
+ ///
/// # Arguments
/// * `public_key`: the public key.
/// * `public_key_metadata`: public key metadata set by the `--public_key_metadata` arg in
@@ -216,6 +218,8 @@ pub trait Ops<'a> {
/// 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 ATX for `partition`, this should forward to `atx_validate_vbmeta_public_key()`.
+ ///
/// # Arguments
/// * `partition`: partition name.
/// * `public_key`: the public key.
@@ -235,7 +239,7 @@ pub trait Ops<'a> {
) -> IoResult<PublicKeyForPartitionInfo>;
}
-/// Info returned from `validare_public_key_for_partition()`.
+/// Info returned from `validate_public_key_for_partition()`.
#[derive(Clone, Copy, Debug)]
pub struct PublicKeyForPartitionInfo {
/// Whether the key is trusted for the given partition..