aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Pursell <dpursell@google.com>2024-04-09 16:38:06 -0700
committerDavid Pursell <dpursell@google.com>2024-04-26 13:39:29 -0700
commitf9d4620d48b2a2b7310bbb56c0c8af821964c5f1 (patch)
treeb6725d91c88dd8c52e9efa011d743a62d1c093dd
parent26eaa0afb46fd0ff56287a8e568fea36ec673f53 (diff)
downloadavb-master.tar.gz
libavb_rs: unlock credential validationHEADmastermain
Implement `cert_validate_unlock_credential()`. Bug: b/320543206 Test: atest libavb_rs_test libavb_rs_uuid_test libavb_rs_unittest libavb_rs_uuid_unittest Change-Id: If2e44d40f88081f43bf97b799c25b32ebf77230a
-rw-r--r--rust/Android.bp8
-rw-r--r--rust/src/cert.rs34
-rw-r--r--rust/src/lib.rs4
-rw-r--r--rust/tests/cert_tests.rs104
-rw-r--r--rust/tests/test_data.rs2
5 files changed, 138 insertions, 14 deletions
diff --git a/rust/Android.bp b/rust/Android.bp
index 756813d..9188819 100644
--- a/rust/Android.bp
+++ b/rust/Android.bp
@@ -35,14 +35,16 @@ rust_defaults {
"--default-enum-style rust",
"--with-derive-default",
"--with-derive-custom=Avb.*Descriptor=FromZeroes,FromBytes",
- "--with-derive-custom=AvbCertPermanentAttributes=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::{FromZeroes, FromBytes};",
+ "--raw-line=use zerocopy::{AsBytes, FromBytes, FromZeroes};",
"--ctypes-prefix=core::ffi",
],
cflags: ["-DBORINGSSL_NO_CXX"],
@@ -292,6 +294,8 @@ rust_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",
diff --git a/rust/src/cert.rs b/rust/src/cert.rs
index 2d673ad..aa54859 100644
--- a/rust/src/cert.rs
+++ b/rust/src/cert.rs
@@ -84,7 +84,10 @@
//! ```
use crate::{error::io_enum_to_result, ops, IoError, IoResult, Ops, PublicKeyForPartitionInfo};
-use avb_bindgen::{avb_cert_generate_unlock_challenge, avb_cert_validate_vbmeta_public_key};
+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;
@@ -288,16 +291,37 @@ pub fn cert_generate_unlock_challenge(cert_ops: &mut dyn CertOps) -> IoResult<Ce
/// # 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,
+ ops: &mut dyn Ops,
+ credential: &CertUnlockCredential,
) -> IoResult<bool> {
- // TODO(b/320543206): implement
- Err(IoError::NotImplemented)
+ // 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.
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index cb8b6d3..99962ab 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -34,8 +34,8 @@ mod verify;
pub use cert::{
cert_generate_unlock_challenge, cert_validate_unlock_credential,
- cert_validate_vbmeta_public_key, CertOps, CertPermanentAttributes, CERT_PIK_VERSION_LOCATION,
- CERT_PSK_VERSION_LOCATION, SHA256_DIGEST_SIZE,
+ 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,
diff --git a/rust/tests/cert_tests.rs b/rust/tests/cert_tests.rs
index 5921b7a..77cd048 100644
--- a/rust/tests/cert_tests.rs
+++ b/rust/tests/cert_tests.rs
@@ -21,14 +21,20 @@ use crate::{
verify_one_image_one_vbmeta,
};
use avb::{
- cert_generate_unlock_challenge, CertPermanentAttributes, IoError, SlotVerifyError,
- CERT_PIK_VERSION_LOCATION, CERT_PSK_VERSION_LOCATION,
+ 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};
-use zerocopy::FromBytes;
+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`.
+/// 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();
@@ -56,9 +62,19 @@ fn build_test_cert_ops_one_image_one_vbmeta<'a>() -> TestOps<'a> {
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];
@@ -193,3 +209,81 @@ fn cert_generate_unlock_challenge_fails_insufficient_rng() {
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
index 3db13d5..4912bd6 100644
--- a/rust/tests/test_data.rs
+++ b/rust/tests/test_data.rs
@@ -57,6 +57,8 @@ 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;