diff options
author | David Pursell <dpursell@google.com> | 2024-04-03 16:48:05 +0000 |
---|---|---|
committer | Gerrit Code Review <noreply-gerritcodereview@google.com> | 2024-04-03 16:48:05 +0000 |
commit | 172666e842431fa1f0e76e8447c8f13cfaef960e (patch) | |
tree | 0636b52d238b69d2d5272a98c08b329f21d089fb | |
parent | fa26a9b3b8a1523f1ec1a8a1a7852bdcd91d78fd (diff) | |
parent | 79c7971629dd8bb6531f2e3c5325f5e172a9679f (diff) | |
download | avb-172666e842431fa1f0e76e8447c8f13cfaef960e.tar.gz |
Merge "libavb_rs: ATX documentation and trait" into main
-rw-r--r-- | rust/bindgen/avb.h | 1 | ||||
-rw-r--r-- | rust/src/atx.rs | 250 | ||||
-rw-r--r-- | rust/src/lib.rs | 6 | ||||
-rw-r--r-- | rust/src/ops.rs | 6 |
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.. |