blob: aa548596d4cfa91d8878fdb78af1744b06408e6e [file] [log] [blame]
// 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)
}
}