// 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 { /// 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 { // 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 { // `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 { // 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 { Err(IoError::NotImplemented) } fn validate_vbmeta_public_key( &mut self, _public_key: &[u8], _public_key_metadata: Option<&[u8]>, ) -> IoResult { Err(IoError::NotImplemented) } fn read_rollback_index(&mut self, _rollback_index_location: usize) -> IoResult { 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 { Err(IoError::NotImplemented) } #[cfg(feature = "uuid")] fn get_unique_guid_for_partition(&mut self, _partition: &CStr) -> IoResult { Err(IoError::NotImplemented) } fn get_size_of_partition(&mut self, _partition: &CStr) -> IoResult { Err(IoError::NotImplemented) } fn read_persistent_value(&mut self, _name: &CStr, _value: &mut [u8]) -> IoResult { 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 { Err(IoError::NotImplemented) } fn cert_ops(&mut self) -> Option<&mut dyn CertOps> { Some(self.cert_ops) } }