Merge "Added HMAC key generation tests."
diff --git a/diced/open_dice_cbor/lib.rs b/diced/open_dice_cbor/lib.rs
index 2859a61..ffb8a48 100644
--- a/diced/open_dice_cbor/lib.rs
+++ b/diced/open_dice_cbor/lib.rs
@@ -74,7 +74,7 @@
pub const SIGNATURE_SIZE: usize = DICE_SIGNATURE_SIZE as usize;
/// Open dice wrapper error type.
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// The libopen-dice backend reported InvalidInput.
#[error("Open dice backend: Invalid input")]
diff --git a/diced/src/permission.rs b/diced/src/permission.rs
index 116df1b..62ca653 100644
--- a/diced/src/permission.rs
+++ b/diced/src/permission.rs
@@ -21,7 +21,7 @@
implement_class!(
/// Permission provides a convenient abstraction from the SELinux class `diced`.
#[selinux(class_name = diced)]
- #[derive(Clone, Copy, Debug, PartialEq)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Permission {
/// Checked when a client attempts to call seal or unseal.
#[selinux(name = use_seal)]
diff --git a/identity/Android.bp b/identity/Android.bp
index 5b8a10e..512e3ad 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -59,7 +59,7 @@
],
static_libs: [
"android.hardware.identity-V4-cpp",
- "android.hardware.keymaster-V4-cpp",
+ "android.hardware.keymaster-V3-cpp",
"libcppbor_external",
],
}
diff --git a/keystore/keystore_cli_v2.cpp b/keystore/keystore_cli_v2.cpp
index 1e9126d..d01c67d 100644
--- a/keystore/keystore_cli_v2.cpp
+++ b/keystore/keystore_cli_v2.cpp
@@ -1025,7 +1025,7 @@
return 1;
}
- auto listener = std::make_shared<ConfirmationListener>();
+ auto listener = ndk::SharedRefBase::make<ConfirmationListener>();
auto future = listener->get_future();
auto rc = apcService->presentPrompt(listener, promptText, extraData, locale, uiOptionsAsFlags);
diff --git a/keystore2/legacykeystore/lib.rs b/keystore2/legacykeystore/lib.rs
index e2d952d..95f917a 100644
--- a/keystore2/legacykeystore/lib.rs
+++ b/keystore2/legacykeystore/lib.rs
@@ -108,6 +108,12 @@
.prepare("SELECT alias FROM profiles WHERE owner = ? ORDER BY alias ASC;")
.context("In list: Failed to prepare statement.")?;
+ // This allow is necessary to avoid the following error:
+ //
+ // error[E0597]: `stmt` does not live long enough
+ //
+ // See: https://github.com/rust-lang/rust-clippy/issues/8114
+ #[allow(clippy::let_and_return)]
let aliases = stmt
.query_map(params![caller_uid], |row| row.get(0))?
.collect::<rusqlite::Result<Vec<String>>>()
@@ -172,7 +178,7 @@
/// This is the main LegacyKeystore error type, it wraps binder exceptions and the
/// LegacyKeystore errors.
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps a LegacyKeystore error code.
#[error("Error::Error({0:?})")]
diff --git a/keystore2/selinux/src/lib.rs b/keystore2/selinux/src/lib.rs
index c0593b7..e5c3091 100644
--- a/keystore2/selinux/src/lib.rs
+++ b/keystore2/selinux/src/lib.rs
@@ -65,7 +65,7 @@
}
/// Selinux Error code.
-#[derive(thiserror::Error, Debug, PartialEq)]
+#[derive(thiserror::Error, Debug, PartialEq, Eq)]
pub enum Error {
/// Indicates that an access check yielded no access.
#[error("Permission Denied")]
diff --git a/keystore2/src/apc.rs b/keystore2/src/apc.rs
index 7d56dc9..1dc14ea 100644
--- a/keystore2/src/apc.rs
+++ b/keystore2/src/apc.rs
@@ -39,7 +39,7 @@
/// This is the main APC error type, it wraps binder exceptions and the
/// APC ResponseCode.
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps an Android Protected Confirmation (APC) response code as defined by the
/// android.security.apc AIDL interface specification.
diff --git a/keystore2/src/authorization.rs b/keystore2/src/authorization.rs
index 8265dd0..666daeb 100644
--- a/keystore2/src/authorization.rs
+++ b/keystore2/src/authorization.rs
@@ -38,7 +38,7 @@
/// This is the Authorization error type, it wraps binder exceptions and the
/// Authorization ResponseCode
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps an IKeystoreAuthorization response code as defined by
/// android.security.authorization AIDL interface specification.
diff --git a/keystore2/src/boot_level_keys.rs b/keystore2/src/boot_level_keys.rs
index 08c52af..237d7d2 100644
--- a/keystore2/src/boot_level_keys.rs
+++ b/keystore2/src/boot_level_keys.rs
@@ -21,26 +21,88 @@
};
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter as KmKeyParameter,
- KeyParameterValue::KeyParameterValue as KmKeyParameterValue, KeyPurpose::KeyPurpose,
- SecurityLevel::SecurityLevel, Tag::Tag,
+ KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
};
use anyhow::{Context, Result};
use keystore2_crypto::{hkdf_expand, ZVec, AES_256_KEY_LENGTH};
use std::{collections::VecDeque, convert::TryFrom};
-fn get_preferred_km_instance_for_level_zero_key() -> Result<KeyMintDevice> {
- let tee = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
- .context("In get_preferred_km_instance_for_level_zero_key: Get TEE instance failed.")?;
- if tee.version() >= KeyMintDevice::KEY_MASTER_V4_1 {
- Ok(tee)
+/// Strategies used to prevent later boot stages from using the KM key that protects the level 0
+/// key
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+enum DenyLaterStrategy {
+ /// set MaxUsesPerBoot to 1. This is much less secure, since the attacker can replace the key
+ /// itself, and therefore create artifacts which appear to come from early boot.
+ MaxUsesPerBoot,
+ /// set the EarlyBootOnly property. This property is only supported in KM from 4.1 on, but
+ /// it ensures that the level 0 key was genuinely created in early boot
+ EarlyBootOnly,
+}
+
+/// Generally the L0 KM and strategy are chosen by probing KM versions in TEE and Strongbox.
+/// However, once a device is launched the KM and strategy must never change, even if the
+/// KM version in TEE or Strongbox is updated. Setting this property at build time using
+/// `PRODUCT_VENDOR_PROPERTIES` means that the strategy can be fixed no matter what versions
+/// of KM are present.
+const PROPERTY_NAME: &str = "ro.keystore.boot_level_key.strategy";
+
+fn lookup_level_zero_km_and_strategy() -> Result<Option<(SecurityLevel, DenyLaterStrategy)>> {
+ let property_val = rustutils::system_properties::read(PROPERTY_NAME).with_context(|| {
+ format!("In lookup_level_zero_km_and_strategy: property read failed: {}", PROPERTY_NAME)
+ })?;
+ // TODO: use feature(let_else) when that's stabilized.
+ let property_val = if let Some(p) = property_val {
+ p
} else {
- match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX).context(
- "In get_preferred_km_instance_for_level_zero_key: Get Strongbox instance failed.",
- )? {
+ log::info!("{} not set, inferring from installed KM instances", PROPERTY_NAME);
+ return Ok(None);
+ };
+ let (level, strategy) = if let Some(c) = property_val.split_once(':') {
+ c
+ } else {
+ log::error!("Missing colon in {}: {:?}", PROPERTY_NAME, property_val);
+ return Ok(None);
+ };
+ let level = match level {
+ "TRUSTED_ENVIRONMENT" => SecurityLevel::TRUSTED_ENVIRONMENT,
+ "STRONGBOX" => SecurityLevel::STRONGBOX,
+ _ => {
+ log::error!("Unknown security level in {}: {:?}", PROPERTY_NAME, level);
+ return Ok(None);
+ }
+ };
+ let strategy = match strategy {
+ "EARLY_BOOT_ONLY" => DenyLaterStrategy::EarlyBootOnly,
+ "MAX_USES_PER_BOOT" => DenyLaterStrategy::MaxUsesPerBoot,
+ _ => {
+ log::error!("Unknown DenyLaterStrategy in {}: {:?}", PROPERTY_NAME, strategy);
+ return Ok(None);
+ }
+ };
+ log::info!("Set from {}: {}", PROPERTY_NAME, property_val);
+ Ok(Some((level, strategy)))
+}
+
+fn get_level_zero_key_km_and_strategy() -> Result<(KeyMintDevice, DenyLaterStrategy)> {
+ if let Some((level, strategy)) = lookup_level_zero_km_and_strategy()? {
+ return Ok((
+ KeyMintDevice::get(level)
+ .context("In get_level_zero_key_km_and_strategy: Get KM instance failed.")?,
+ strategy,
+ ));
+ }
+ let tee = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT)
+ .context("In get_level_zero_key_km_and_strategy: Get TEE instance failed.")?;
+ if tee.version() >= KeyMintDevice::KEY_MASTER_V4_1 {
+ Ok((tee, DenyLaterStrategy::EarlyBootOnly))
+ } else {
+ match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX)
+ .context("In get_level_zero_key_km_and_strategy: Get Strongbox instance failed.")?
+ {
Some(strongbox) if strongbox.version() >= KeyMintDevice::KEY_MASTER_V4_1 => {
- Ok(strongbox)
+ Ok((strongbox, DenyLaterStrategy::EarlyBootOnly))
}
- _ => Ok(tee),
+ _ => Ok((tee, DenyLaterStrategy::MaxUsesPerBoot)),
}
}
}
@@ -49,52 +111,49 @@
/// In practice the caller is SuperKeyManager and the lock is the
/// Mutex on its internal state.
pub fn get_level_zero_key(db: &mut KeystoreDB) -> Result<ZVec> {
- let km_dev = get_preferred_km_instance_for_level_zero_key()
+ let (km_dev, deny_later_strategy) = get_level_zero_key_km_and_strategy()
.context("In get_level_zero_key: get preferred KM instance failed")?;
-
- let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string());
- let mut params = vec![
+ log::info!(
+ "In get_level_zero_key: security_level={:?}, deny_later_strategy={:?}",
+ km_dev.security_level(),
+ deny_later_strategy
+ );
+ let required_security_level = km_dev.security_level();
+ let required_param: KmKeyParameter = match deny_later_strategy {
+ DenyLaterStrategy::EarlyBootOnly => KeyParameterValue::EarlyBootOnly,
+ DenyLaterStrategy::MaxUsesPerBoot => KeyParameterValue::MaxUsesPerBoot(1),
+ }
+ .into();
+ let params = vec![
KeyParameterValue::Algorithm(Algorithm::HMAC).into(),
KeyParameterValue::Digest(Digest::SHA_2_256).into(),
KeyParameterValue::KeySize(256).into(),
KeyParameterValue::MinMacLength(256).into(),
KeyParameterValue::KeyPurpose(KeyPurpose::SIGN).into(),
KeyParameterValue::NoAuthRequired.into(),
+ required_param.clone(),
];
- let has_early_boot_only = km_dev.version() >= KeyMintDevice::KEY_MASTER_V4_1;
-
- if has_early_boot_only {
- params.push(KeyParameterValue::EarlyBootOnly.into());
- } else {
- params.push(KeyParameterValue::MaxUsesPerBoot(1).into())
- }
-
+ let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string());
let (key_id_guard, key_entry) = km_dev
.lookup_or_generate_key(db, &key_desc, KeyType::Client, ¶ms, |key_characteristics| {
key_characteristics.iter().any(|kc| {
- if kc.securityLevel == km_dev.security_level() {
- kc.authorizations.iter().any(|a| {
- matches!(
- (has_early_boot_only, a),
- (
- true,
- KmKeyParameter {
- tag: Tag::EARLY_BOOT_ONLY,
- value: KmKeyParameterValue::BoolValue(true)
- }
- ) | (
- false,
- KmKeyParameter {
- tag: Tag::MAX_USES_PER_BOOT,
- value: KmKeyParameterValue::Integer(1)
- }
- )
- )
- })
- } else {
- false
+ if kc.securityLevel != required_security_level {
+ log::error!(
+ "In get_level_zero_key: security level expected={:?} got={:?}",
+ required_security_level,
+ kc.securityLevel
+ );
+ return false;
}
+ if !kc.authorizations.iter().any(|a| a == &required_param) {
+ log::error!(
+ "In get_level_zero_key: required param absent {:?}",
+ required_param
+ );
+ return false;
+ }
+ true
})
})
.context("In get_level_zero_key: lookup_or_generate_key failed")?;
diff --git a/keystore2/src/crypto/crypto.cpp b/keystore2/src/crypto/crypto.cpp
index 6de3be7..7feeaff 100644
--- a/keystore2/src/crypto/crypto.cpp
+++ b/keystore2/src/crypto/crypto.cpp
@@ -18,6 +18,7 @@
#include "crypto.hpp"
+#include <assert.h>
#include <log/log.h>
#include <openssl/aes.h>
#include <openssl/ec.h>
diff --git a/keystore2/src/crypto/include/certificate_utils.h b/keystore2/src/crypto/include/certificate_utils.h
index cad82b6..13d3ef0 100644
--- a/keystore2/src/crypto/include/certificate_utils.h
+++ b/keystore2/src/crypto/include/certificate_utils.h
@@ -20,6 +20,7 @@
#include <openssl/x509.h>
#include <stdint.h>
+#include <functional>
#include <memory>
#include <optional>
#include <variant>
diff --git a/keystore2/src/crypto/lib.rs b/keystore2/src/crypto/lib.rs
index e925180..7ba47c8 100644
--- a/keystore2/src/crypto/lib.rs
+++ b/keystore2/src/crypto/lib.rs
@@ -190,7 +190,7 @@
fn get_key(&'a self) -> &'a [u8] {
match self {
Self::Ref(b) => b,
- Self::Owned(z) => &*z,
+ Self::Owned(z) => z,
}
}
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index f34c5da..b60b64f 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -41,7 +41,7 @@
/// This is the main Keystore error type. It wraps the Keystore `ResponseCode` generated
/// from AIDL in the `Rc` variant and Keymint `ErrorCode` in the Km variant.
-#[derive(Debug, thiserror::Error, PartialEq)]
+#[derive(Debug, thiserror::Error, PartialEq, Eq)]
pub enum Error {
/// Wraps a Keystore `ResponseCode` as defined by the Keystore AIDL interface specification.
#[error("Error::Rc({0:?})")]
diff --git a/keystore2/src/fuzzers/Android.bp b/keystore2/src/fuzzers/Android.bp
index 384ab77..3adb922 100644
--- a/keystore2/src/fuzzers/Android.bp
+++ b/keystore2/src/fuzzers/Android.bp
@@ -17,13 +17,23 @@
}
rust_fuzz {
- name: "legacy_blob_fuzzer",
- srcs: ["legacy_blob_fuzzer.rs"],
+ name: "keystore2_unsafe_fuzzer",
+ srcs: ["keystore2_unsafe_fuzzer.rs"],
rustlibs: [
"libkeystore2",
+ "libkeystore2_crypto_rust",
+ "libkeystore2_vintf_rust",
+ "libkeystore2_aaid-rust",
+ "libkeystore2_apc_compat-rust",
+ "libkeystore2_selinux",
+ "libarbitrary",
],
fuzz_config: {
fuzz_on_haiku_device: true,
fuzz_on_haiku_host: false,
+ cc: [
+ "android-media-fuzzing-reports@google.com",
+ ],
+ componentid: 155276,
},
}
diff --git a/keystore2/src/fuzzers/README.md b/keystore2/src/fuzzers/README.md
new file mode 100644
index 0000000..a4ed095
--- /dev/null
+++ b/keystore2/src/fuzzers/README.md
@@ -0,0 +1,18 @@
+# Fuzzers for libkeystore2
+## Table of contents
++ [keystore2_unsafe_fuzzer](#Keystore2Unsafe)
+
+# <a name="Keystore2Unsafe"></a> Fuzzer for Keystore2Unsafe
+All the parameters of Keystore2Unsafe are populated randomly from libfuzzer. You can find the possible values in the fuzzer's source code.
+
+#### Steps to run
+1. Build the fuzzer
+```
+$ m -j$(nproc) keystore2_unsafe_fuzzer
+```
+
+2. Run on device
+```
+$ adb sync data
+$ adb shell /data/fuzz/${TARGET_ARCH}/keystore2_unsafe_fuzzer/keystore2_unsafe_fuzzer
+```
diff --git a/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
new file mode 100644
index 0000000..4c2419a
--- /dev/null
+++ b/keystore2/src/fuzzers/keystore2_unsafe_fuzzer.rs
@@ -0,0 +1,248 @@
+// Copyright 2022, 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.
+
+//! Fuzzes unsafe APIs of libkeystore2 module
+
+#![feature(slice_internals)]
+#![no_main]
+#[macro_use]
+extern crate libfuzzer_sys;
+
+use core::slice::memchr;
+use keystore2::{legacy_blob::LegacyBlobLoader, utils::ui_opts_2_compat};
+use keystore2_aaid::get_aaid;
+use keystore2_apc_compat::ApcHal;
+use keystore2_crypto::{
+ aes_gcm_decrypt, aes_gcm_encrypt, ec_key_generate_key, ec_key_get0_public_key,
+ ec_key_marshal_private_key, ec_key_parse_private_key, ec_point_oct_to_point,
+ ec_point_point_to_oct, ecdh_compute_key, generate_random_data, hkdf_expand, hkdf_extract,
+ hmac_sha256, parse_subject_from_certificate, Password, ZVec,
+};
+use keystore2_selinux::{check_access, getpidcon, setcon, Backend, Context, KeystoreKeyBackend};
+use keystore2_vintf::{get_aidl_instances, get_hidl_instances};
+use libfuzzer_sys::arbitrary::Arbitrary;
+use std::{ffi::CString, sync::Arc};
+
+// Avoid allocating too much memory and crashing the fuzzer.
+const MAX_SIZE_MODIFIER: usize = 1024;
+
+/// CString does not contain any internal 0 bytes
+fn get_valid_cstring_data(data: &[u8]) -> &[u8] {
+ match memchr::memchr(0, data) {
+ Some(idx) => &data[0..idx],
+ None => data,
+ }
+}
+
+#[derive(Arbitrary, Debug)]
+enum FuzzCommand<'a> {
+ DecodeAlias {
+ string: String,
+ },
+ TryFrom {
+ vector_data: Vec<u8>,
+ },
+ GenerateRandomData {
+ size: usize,
+ },
+ HmacSha256 {
+ key_hmac: &'a [u8],
+ msg: &'a [u8],
+ },
+ AesGcmDecrypt {
+ data: &'a [u8],
+ iv: &'a [u8],
+ tag: &'a [u8],
+ key_aes_decrypt: &'a [u8],
+ },
+ AesGcmEecrypt {
+ plaintext: &'a [u8],
+ key_aes_encrypt: &'a [u8],
+ },
+ Password {
+ pw: &'a [u8],
+ salt: &'a [u8],
+ key_length: usize,
+ },
+ HkdfExtract {
+ hkdf_secret: &'a [u8],
+ hkdf_salt: &'a [u8],
+ },
+ HkdfExpand {
+ out_len: usize,
+ hkdf_prk: &'a [u8],
+ hkdf_info: &'a [u8],
+ },
+ PublicPrivateKey {
+ ec_priv_buf: &'a [u8],
+ ec_oct_buf: &'a [u8],
+ },
+ ParseSubjectFromCertificate {
+ parse_buf: &'a [u8],
+ },
+ GetHidlInstances {
+ hidl_package: &'a str,
+ major_version: usize,
+ minor_version: usize,
+ hidl_interface_name: &'a str,
+ },
+ GetAidlInstances {
+ aidl_package: &'a str,
+ version: usize,
+ aidl_interface_name: &'a str,
+ },
+ GetAaid {
+ aaid_uid: u32,
+ },
+ Hal {
+ opt: i32,
+ prompt_text: &'a str,
+ locale: &'a str,
+ extra_data: &'a [u8],
+ },
+ Context {
+ context: &'a str,
+ },
+ Backend {
+ namespace: &'a str,
+ },
+ GetPidCon {
+ pid: i32,
+ },
+ CheckAccess {
+ source: &'a [u8],
+ target: &'a [u8],
+ tclass: &'a str,
+ perm: &'a str,
+ },
+ SetCon {
+ set_target: &'a [u8],
+ },
+}
+
+fuzz_target!(|commands: Vec<FuzzCommand>| {
+ for command in commands {
+ match command {
+ FuzzCommand::DecodeAlias { string } => {
+ let _res = LegacyBlobLoader::decode_alias(&string);
+ }
+ FuzzCommand::TryFrom { vector_data } => {
+ let _res = ZVec::try_from(vector_data);
+ }
+ FuzzCommand::GenerateRandomData { size } => {
+ let _res = generate_random_data(size % MAX_SIZE_MODIFIER);
+ }
+ FuzzCommand::HmacSha256 { key_hmac, msg } => {
+ let _res = hmac_sha256(key_hmac, msg);
+ }
+ FuzzCommand::AesGcmDecrypt { data, iv, tag, key_aes_decrypt } => {
+ let _res = aes_gcm_decrypt(data, iv, tag, key_aes_decrypt);
+ }
+ FuzzCommand::AesGcmEecrypt { plaintext, key_aes_encrypt } => {
+ let _res = aes_gcm_encrypt(plaintext, key_aes_encrypt);
+ }
+ FuzzCommand::Password { pw, salt, key_length } => {
+ let _res = Password::from(pw).derive_key(salt, key_length % MAX_SIZE_MODIFIER);
+ }
+ FuzzCommand::HkdfExtract { hkdf_secret, hkdf_salt } => {
+ let _res = hkdf_extract(hkdf_secret, hkdf_salt);
+ }
+ FuzzCommand::HkdfExpand { out_len, hkdf_prk, hkdf_info } => {
+ let _res = hkdf_expand(out_len % MAX_SIZE_MODIFIER, hkdf_prk, hkdf_info);
+ }
+ FuzzCommand::PublicPrivateKey { ec_priv_buf, ec_oct_buf } => {
+ let check_private_key = {
+ let mut check_private_key = ec_key_parse_private_key(ec_priv_buf);
+ if check_private_key.is_err() {
+ check_private_key = ec_key_generate_key();
+ };
+ check_private_key
+ };
+ let check_ecpoint = ec_point_oct_to_point(ec_oct_buf);
+ if check_private_key.is_ok() {
+ let private_key = check_private_key.unwrap();
+ ec_key_get0_public_key(&private_key);
+ let _res = ec_key_marshal_private_key(&private_key);
+
+ if check_ecpoint.is_ok() {
+ let public_key = check_ecpoint.unwrap();
+ let _res = ec_point_point_to_oct(public_key.get_point());
+ let _res = ecdh_compute_key(public_key.get_point(), &private_key);
+ }
+ }
+ }
+ FuzzCommand::ParseSubjectFromCertificate { parse_buf } => {
+ let _res = parse_subject_from_certificate(parse_buf);
+ }
+ FuzzCommand::GetHidlInstances {
+ hidl_package,
+ major_version,
+ minor_version,
+ hidl_interface_name,
+ } => {
+ get_hidl_instances(hidl_package, major_version, minor_version, hidl_interface_name);
+ }
+ FuzzCommand::GetAidlInstances { aidl_package, version, aidl_interface_name } => {
+ get_aidl_instances(aidl_package, version, aidl_interface_name);
+ }
+ FuzzCommand::GetAaid { aaid_uid } => {
+ let _res = get_aaid(aaid_uid);
+ }
+ FuzzCommand::Hal { opt, prompt_text, locale, extra_data } => {
+ let hal = ApcHal::try_get_service();
+ if hal.is_some() {
+ let hal = Arc::new(hal.unwrap());
+ let apc_compat_options = ui_opts_2_compat(opt);
+ let prompt_text =
+ std::str::from_utf8(get_valid_cstring_data(prompt_text.as_bytes()))
+ .unwrap();
+ let locale =
+ std::str::from_utf8(get_valid_cstring_data(locale.as_bytes())).unwrap();
+ let _res = hal.prompt_user_confirmation(
+ prompt_text,
+ extra_data,
+ locale,
+ apc_compat_options,
+ move |_, _, _| {},
+ );
+ }
+ }
+ FuzzCommand::Context { context } => {
+ let _res = Context::new(context);
+ }
+ FuzzCommand::Backend { namespace } => {
+ let backend = KeystoreKeyBackend::new();
+ if let Ok(backend) = backend {
+ let _res = backend.lookup(namespace);
+ }
+ }
+ FuzzCommand::GetPidCon { pid } => {
+ let _res = getpidcon(pid);
+ }
+ FuzzCommand::CheckAccess { source, target, tclass, perm } => {
+ let source = get_valid_cstring_data(source);
+ let target = get_valid_cstring_data(target);
+ let _res = check_access(
+ &CString::new(source).unwrap(),
+ &CString::new(target).unwrap(),
+ tclass,
+ perm,
+ );
+ }
+ FuzzCommand::SetCon { set_target } => {
+ let _res = setcon(&CString::new(get_valid_cstring_data(set_target)).unwrap());
+ }
+ }
+ }
+});
diff --git a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs b/keystore2/src/fuzzers/legacy_blob_fuzzer.rs
deleted file mode 100644
index 7e3e848..0000000
--- a/keystore2/src/fuzzers/legacy_blob_fuzzer.rs
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright 2021, 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.
-
-#![allow(missing_docs)]
-#![no_main]
-#[macro_use]
-extern crate libfuzzer_sys;
-use keystore2::legacy_blob::LegacyBlobLoader;
-
-fuzz_target!(|data: &[u8]| {
- if !data.is_empty() {
- let string = data.iter().filter_map(|c| std::char::from_u32(*c as u32)).collect::<String>();
- let _res = LegacyBlobLoader::decode_alias(&string);
- }
-});
diff --git a/keystore2/src/globals.rs b/keystore2/src/globals.rs
index 70b78ba..edbe6ce 100644
--- a/keystore2/src/globals.rs
+++ b/keystore2/src/globals.rs
@@ -186,7 +186,7 @@
Box::new(|uuid, blob| {
let km_dev = get_keymint_dev_by_uuid(uuid).map(|(dev, _)| dev)?;
let _wp = wd::watch_millis("In invalidate key closure: calling deleteKey", 500);
- map_km_error(km_dev.deleteKey(&*blob))
+ map_km_error(km_dev.deleteKey(blob))
.context("In invalidate key closure: Trying to invalidate key blob.")
}),
KeystoreDB::new(&DB_PATH.read().expect("Could not get the database directory."), None)
diff --git a/keystore2/src/metrics_store.rs b/keystore2/src/metrics_store.rs
index 5e88052..62a7d13 100644
--- a/keystore2/src/metrics_store.rs
+++ b/keystore2/src/metrics_store.rs
@@ -600,10 +600,8 @@
/// Log error events related to Remote Key Provisioning (RKP).
pub fn log_rkp_error_stats(rkp_error: MetricsRkpError, sec_level: &SecurityLevel) {
- let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats(RkpErrorStats {
- rkpError: rkp_error,
- security_level: process_security_level(*sec_level),
- });
+ let rkp_error_stats = KeystoreAtomPayload::RkpErrorStats(
+ RkpErrorStats { rkpError: rkp_error, security_level: process_security_level(*sec_level) });
METRICS_STORE.insert_atom(AtomID::RKP_ERROR_STATS, rkp_error_stats);
}
diff --git a/keystore2/src/operation.rs b/keystore2/src/operation.rs
index 5da3b32..4f33ba6 100644
--- a/keystore2/src/operation.rs
+++ b/keystore2/src/operation.rs
@@ -790,7 +790,7 @@
Ok(mut mutex_guard) => {
let result = match &*mutex_guard {
Some(op) => {
- let result = f(&*op);
+ let result = f(op);
// Any error here means we can discard the operation.
if result.is_err() {
delete_op = true;
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index 3cc116b..f012c1b 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -54,7 +54,7 @@
/// the SELinux permissions.
#[repr(i32)]
#[selinux(class_name = keystore2_key)]
- #[derive(Clone, Copy, Debug, PartialEq)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeyPerm {
/// Checked when convert_storage_key_to_ephemeral is called.
#[selinux(name = convert_storage_key_to_ephemeral)]
@@ -100,7 +100,7 @@
/// KeystorePerm provides a convenient abstraction from the SELinux class `keystore2`.
/// Using the implement_permission macro we get the same features as `KeyPerm`.
#[selinux(class_name = keystore2)]
- #[derive(Clone, Copy, Debug, PartialEq)]
+ #[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum KeystorePerm {
/// Checked when a new auth token is installed.
#[selinux(name = add_auth)]
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index 8ed2be4..ea2698f 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -159,10 +159,8 @@
if self.is_rkp_only() {
return Err(e);
}
- log_rkp_error_stats(
- MetricsRkpError::FALL_BACK_DURING_HYBRID,
- &self.security_level,
- );
+ log_rkp_error_stats(MetricsRkpError::FALL_BACK_DURING_HYBRID,
+ &self.security_level);
Ok(None)
}
Ok(v) => match v {
diff --git a/keystore2/tests/keystore2_client_operation_tests.rs b/keystore2/tests/keystore2_client_operation_tests.rs
index d8b85f6..e1102dd 100644
--- a/keystore2/tests/keystore2_client_operation_tests.rs
+++ b/keystore2/tests/keystore2_client_operation_tests.rs
@@ -14,12 +14,15 @@
use nix::unistd::{getuid, Gid, Uid};
use rustutils::users::AID_USER_OFFSET;
+use std::thread;
+use std::thread::JoinHandle;
use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
Digest::Digest, ErrorCode::ErrorCode, KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel,
};
use android_system_keystore2::aidl::android::system::keystore2::{
- CreateOperationResponse::CreateOperationResponse, Domain::Domain, ResponseCode::ResponseCode,
+ CreateOperationResponse::CreateOperationResponse, Domain::Domain,
+ IKeystoreOperation::IKeystoreOperation, ResponseCode::ResponseCode,
};
use keystore2_test_utils::{
@@ -57,6 +60,25 @@
.collect()
}
+/// Executes an operation in a thread. Expect an `OPERATION_BUSY` error in case of operation
+/// failure. Returns True if `OPERATION_BUSY` error is encountered otherwise returns false.
+fn perform_op_busy_in_thread(op: binder::Strong<dyn IKeystoreOperation>) -> JoinHandle<bool> {
+ thread::spawn(move || {
+ for _n in 1..1000 {
+ match key_generations::map_ks_error(op.update(b"my message")) {
+ Ok(_) => continue,
+ Err(e) => {
+ assert_eq!(Error::Rc(ResponseCode::OPERATION_BUSY), e);
+ return true;
+ }
+ }
+ }
+ let sig = op.finish(None, None).unwrap();
+ assert!(sig.is_some());
+ false
+ })
+}
+
/// This test verifies that backend service throws BACKEND_BUSY error when all
/// operations slots are full. This test creates operations in child processes and
/// collects the status of operations performed in each child proc and determines
@@ -402,3 +424,29 @@
});
}
}
+
+/// Create an operation and try to use this operation handle in multiple threads to perform
+/// operations. Test should fail to perform an operation with an error response `OPERATION_BUSY`
+/// when multiple threads try to access the operation handle at same time.
+#[test]
+fn keystore2_op_fails_operation_busy() {
+ let op_response = create_signing_operation(
+ ForcedOp(false),
+ KeyPurpose::SIGN,
+ Digest::SHA_2_256,
+ Domain::APP,
+ -1,
+ Some("op_busy_alias_test_key".to_string()),
+ )
+ .unwrap();
+
+ let op: binder::Strong<dyn IKeystoreOperation> = op_response.iOperation.unwrap();
+
+ let th_handle_1 = perform_op_busy_in_thread(op.clone());
+ let th_handle_2 = perform_op_busy_in_thread(op);
+
+ let result1 = th_handle_1.join().unwrap();
+ let result2 = th_handle_2.join().unwrap();
+
+ assert!(result1 || result2);
+}
diff --git a/provisioner/Android.bp b/provisioner/Android.bp
index 665a9e7..87f39d0 100644
--- a/provisioner/Android.bp
+++ b/provisioner/Android.bp
@@ -43,12 +43,10 @@
},
}
-cc_binary {
- name: "rkp_factory_extraction_tool",
- vendor: true,
- srcs: ["rkp_factory_extraction_tool.cpp"],
+cc_defaults {
+ name: "rkp_factory_extraction_defaults",
defaults: [
- "keymint_use_latest_hal_aidl_ndk_shared",
+ "keymint_use_latest_hal_aidl_ndk_static",
],
shared_libs: [
"libbinder",
@@ -60,8 +58,42 @@
"libbase",
"libcppbor_external",
"libcppcose_rkp",
- "libgflags",
"libjsoncpp",
"libkeymint_remote_prov_support",
],
}
+
+cc_library_static {
+ name: "librkp_factory_extraction",
+ defaults: [
+ "rkp_factory_extraction_defaults",
+ ],
+ srcs: ["rkp_factory_extraction_lib.cpp"],
+ vendor_available: true,
+}
+
+cc_test {
+ name: "librkp_factory_extraction_test",
+ defaults: [
+ "rkp_factory_extraction_defaults",
+ ],
+ srcs: ["rkp_factory_extraction_lib_test.cpp"],
+ test_suites: ["device-tests"],
+ static_libs: [
+ "libgmock",
+ "librkp_factory_extraction",
+ ],
+}
+
+cc_binary {
+ name: "rkp_factory_extraction_tool",
+ vendor: true,
+ srcs: ["rkp_factory_extraction_tool.cpp"],
+ defaults: [
+ "rkp_factory_extraction_defaults",
+ ],
+ static_libs: [
+ "libgflags",
+ "librkp_factory_extraction",
+ ],
+}
diff --git a/provisioner/TEST_MAPPING b/provisioner/TEST_MAPPING
new file mode 100644
index 0000000..de3f165
--- /dev/null
+++ b/provisioner/TEST_MAPPING
@@ -0,0 +1,7 @@
+{
+ "presubmit": [
+ {
+ "name": "librkp_factory_extraction_test"
+ }
+ ]
+}
diff --git a/provisioner/rkp_factory_extraction_lib.cpp b/provisioner/rkp_factory_extraction_lib.cpp
new file mode 100644
index 0000000..3bf3d7e
--- /dev/null
+++ b/provisioner/rkp_factory_extraction_lib.cpp
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include "rkp_factory_extraction_lib.h"
+
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <android/binder_manager.h>
+#include <cppbor.h>
+#include <keymaster/cppcose/cppcose.h>
+#include <openssl/base64.h>
+#include <remote_prov/remote_prov_utils.h>
+#include <sys/random.h>
+
+#include <memory>
+#include <optional>
+#include <string>
+#include <string_view>
+#include <vector>
+
+#include "cppbor_parse.h"
+
+using aidl::android::hardware::security::keymint::DeviceInfo;
+using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent;
+using aidl::android::hardware::security::keymint::MacedPublicKey;
+using aidl::android::hardware::security::keymint::ProtectedData;
+using aidl::android::hardware::security::keymint::RpcHardwareInfo;
+using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain;
+using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild;
+
+using namespace cppbor;
+using namespace cppcose;
+
+std::string toBase64(const std::vector<uint8_t>& buffer) {
+ size_t base64Length;
+ int rc = EVP_EncodedLength(&base64Length, buffer.size());
+ if (!rc) {
+ std::cerr << "Error getting base64 length. Size overflow?" << std::endl;
+ exit(-1);
+ }
+
+ std::string base64(base64Length, ' ');
+ rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size());
+ ++rc; // Account for NUL, which BoringSSL does not for some reason.
+ if (rc != base64Length) {
+ std::cerr << "Error writing base64. Expected " << base64Length
+ << " bytes to be written, but " << rc << " bytes were actually written."
+ << std::endl;
+ exit(-1);
+ }
+
+ // BoringSSL automatically adds a NUL -- remove it from the string data
+ base64.pop_back();
+
+ return base64;
+}
+
+std::vector<uint8_t> generateChallenge() {
+ std::vector<uint8_t> challenge(kChallengeSize);
+
+ ssize_t bytesRemaining = static_cast<ssize_t>(challenge.size());
+ uint8_t* writePtr = challenge.data();
+ while (bytesRemaining > 0) {
+ int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0);
+ if (bytesRead < 0) {
+ if (errno == EINTR) {
+ continue;
+ } else {
+ std::cerr << errno << ": " << strerror(errno) << std::endl;
+ exit(-1);
+ }
+ }
+ bytesRemaining -= bytesRead;
+ writePtr += bytesRead;
+ }
+
+ return challenge;
+}
+
+CsrResult composeCertificateRequest(const ProtectedData& protectedData,
+ const DeviceInfo& verifiedDeviceInfo,
+ const std::vector<uint8_t>& challenge,
+ const std::vector<uint8_t>& keysToSignMac) {
+ Array macedKeysToSign = Array()
+ .add(Map().add(1, 5).encode()) // alg: hmac-sha256
+ .add(Map()) // empty unprotected headers
+ .add(Null()) // nil for the payload
+ .add(keysToSignMac); // MAC as returned from the HAL
+
+ auto [parsedVerifiedDeviceInfo, ignore1, errMsg] = parse(verifiedDeviceInfo.deviceInfo);
+ if (!parsedVerifiedDeviceInfo) {
+ std::cerr << "Error parsing device info: '" << errMsg << "'" << std::endl;
+ return {nullptr, errMsg};
+ }
+
+ auto [parsedProtectedData, ignore2, errMsg2] = parse(protectedData.protectedData);
+ if (!parsedProtectedData) {
+ std::cerr << "Error parsing protected data: '" << errMsg2 << "'" << std::endl;
+ return {nullptr, errMsg};
+ }
+
+ Array deviceInfo = Array().add(std::move(parsedVerifiedDeviceInfo)).add(Map());
+
+ auto certificateRequest = std::make_unique<Array>();
+ (*certificateRequest)
+ .add(std::move(deviceInfo))
+ .add(challenge)
+ .add(std::move(parsedProtectedData))
+ .add(std::move(macedKeysToSign));
+ return {std::move(certificateRequest), std::nullopt};
+}
+
+CsrResult getCsr(std::string_view componentName, IRemotelyProvisionedComponent* irpc) {
+ std::vector<uint8_t> keysToSignMac;
+ std::vector<MacedPublicKey> emptyKeys;
+ DeviceInfo verifiedDeviceInfo;
+ ProtectedData protectedData;
+ RpcHardwareInfo hwInfo;
+ ::ndk::ScopedAStatus status = irpc->getHardwareInfo(&hwInfo);
+ if (!status.isOk()) {
+ std::cerr << "Failed to get hardware info for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+
+ const std::vector<uint8_t> eek = getProdEekChain(hwInfo.supportedEekCurve);
+ const std::vector<uint8_t> challenge = generateChallenge();
+ status = irpc->generateCertificateRequest(
+ /*test_mode=*/false, emptyKeys, eek, challenge, &verifiedDeviceInfo, &protectedData,
+ &keysToSignMac);
+ if (!status.isOk()) {
+ std::cerr << "Bundle extraction failed for '" << componentName
+ << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ exit(-1);
+ }
+ return composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge, keysToSignMac);
+}
diff --git a/provisioner/rkp_factory_extraction_lib.h b/provisioner/rkp_factory_extraction_lib.h
new file mode 100644
index 0000000..fe15402
--- /dev/null
+++ b/provisioner/rkp_factory_extraction_lib.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2022 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.
+ */
+
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <android/binder_manager.h>
+#include <cppbor.h>
+#include <keymaster/cppcose/cppcose.h>
+
+#include <cstdint>
+#include <memory>
+#include <optional>
+#include <string>
+#include <vector>
+
+constexpr size_t kChallengeSize = 16;
+
+// Contains the result of CSR generation, bundling up the result (on success)
+// with an error message (on failure).
+struct CsrResult {
+ std::unique_ptr<cppbor::Array> csr;
+ std::optional<std::string> errMsg;
+};
+
+// Return `buffer` encoded as a base64 string.
+std::string toBase64(const std::vector<uint8_t>& buffer);
+
+// Generate a random challenge containing `kChallengeSize` bytes.
+std::vector<uint8_t> generateChallenge();
+
+// Get a certificate signing request for the given IRemotelyProvisionedComponent.
+// On error, the csr Array is null, and the string field contains a description of
+// what went wrong.
+CsrResult getCsr(std::string_view componentName,
+ aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent* irpc);
\ No newline at end of file
diff --git a/provisioner/rkp_factory_extraction_lib_test.cpp b/provisioner/rkp_factory_extraction_lib_test.cpp
new file mode 100644
index 0000000..c611097
--- /dev/null
+++ b/provisioner/rkp_factory_extraction_lib_test.cpp
@@ -0,0 +1,204 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+#include "rkp_factory_extraction_lib.h"
+
+#include <aidl/android/hardware/security/keymint/DeviceInfo.h>
+#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
+#include <aidl/android/hardware/security/keymint/MacedPublicKey.h>
+#include <aidl/android/hardware/security/keymint/RpcHardwareInfo.h>
+#include <gmock/gmock.h>
+#include <gtest/gtest.h>
+
+#include <cstdint>
+#include <ostream>
+#include <set>
+#include <vector>
+
+#include "aidl/android/hardware/security/keymint/ProtectedData.h"
+#include "android/binder_auto_utils.h"
+#include "android/binder_interface_utils.h"
+#include "cppbor.h"
+
+using ::ndk::ScopedAStatus;
+using ::ndk::SharedRefBase;
+
+using namespace ::aidl::android::hardware::security::keymint;
+using namespace ::cppbor;
+using namespace ::testing;
+
+namespace cppbor {
+
+std::ostream& operator<<(std::ostream& os, const Item& item) {
+ return os << prettyPrint(&item);
+}
+
+std::ostream& operator<<(std::ostream& os, const std::unique_ptr<Item>& item) {
+ return os << *item;
+}
+
+std::ostream& operator<<(std::ostream& os, const Item* item) {
+ return os << *item;
+}
+
+} // namespace cppbor
+
+class MockIRemotelyProvisionedComponent : public IRemotelyProvisionedComponentDefault {
+ public:
+ MOCK_METHOD(ScopedAStatus, getHardwareInfo, (RpcHardwareInfo * _aidl_return), (override));
+ MOCK_METHOD(ScopedAStatus, generateEcdsaP256KeyPair,
+ (bool in_testMode, MacedPublicKey* out_macedPublicKey,
+ std::vector<uint8_t>* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, generateCertificateRequest,
+ (bool in_testMode, const std::vector<MacedPublicKey>& in_keysToSign,
+ const std::vector<uint8_t>& in_endpointEncryptionCertChain,
+ const std::vector<uint8_t>& in_challenge, DeviceInfo* out_deviceInfo,
+ ProtectedData* out_protectedData, std::vector<uint8_t>* _aidl_return),
+ (override));
+ MOCK_METHOD(ScopedAStatus, getInterfaceVersion, (int32_t * _aidl_return), (override));
+ MOCK_METHOD(ScopedAStatus, getInterfaceHash, (std::string * _aidl_return), (override));
+};
+
+TEST(LibRkpFactoryExtractionTests, ToBase64) {
+ std::vector<uint8_t> input(UINT8_MAX + 1);
+ for (int i = 0; i < input.size(); ++i) {
+ input[i] = i;
+ }
+
+ // Test three lengths so we get all the different paddding options
+ EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
+ "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
+ "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
+ "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8"
+ "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv"
+ "s7e7v8PHy8/T19vf4+fr7/P3+/w==",
+ toBase64(input));
+
+ input.push_back(42);
+ EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
+ "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
+ "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
+ "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8"
+ "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv"
+ "s7e7v8PHy8/T19vf4+fr7/P3+/yo=",
+ toBase64(input));
+
+ input.push_back(42);
+ EXPECT_EQ("AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4"
+ "vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV"
+ "5fYGFiY2RlZmdoaWprbG1ub3BxcnN0dXZ3eHl6e3x9fn+AgYKDhIWGh4iJiouMj"
+ "Y6PkJGSk5SVlpeYmZqbnJ2en6ChoqOkpaanqKmqq6ytrq+wsbKztLW2t7i5uru8"
+ "vb6/wMHCw8TFxsfIycrLzM3Oz9DR0tPU1dbX2Nna29zd3t/g4eLj5OXm5+jp6uv"
+ "s7e7v8PHy8/T19vf4+fr7/P3+/yoq",
+ toBase64(input));
+}
+
+TEST(LibRkpFactoryExtractionTests, UniqueChallengeSmokeTest) {
+ // This will at least catch VERY broken implementations.
+ constexpr size_t NUM_CHALLENGES = 32;
+ std::set<std::vector<uint8_t>> challenges;
+ for (size_t i = 0; i < NUM_CHALLENGES; ++i) {
+ const std::vector<uint8_t> challenge = generateChallenge();
+ const auto [_, wasInserted] = challenges.insert(generateChallenge());
+ EXPECT_TRUE(wasInserted) << "Duplicate challenge: " << toBase64(challenge);
+ }
+}
+
+TEST(LibRkpFactoryExtractionTests, GetCsrWithV2Hal) {
+ ASSERT_TRUE(true);
+
+ const std::vector<uint8_t> kFakeMac = {1, 2, 3, 4};
+
+ Map cborDeviceInfo;
+ cborDeviceInfo.add("product", "gShoe");
+ cborDeviceInfo.add("version", 2);
+ const DeviceInfo kVerifiedDeviceInfo = {cborDeviceInfo.encode()};
+
+ Array cborProtectedData;
+ cborProtectedData.add(Bstr()); // protected
+ cborProtectedData.add(Map()); // unprotected
+ cborProtectedData.add(Bstr()); // ciphertext
+ cborProtectedData.add(Array()); // recipients
+ const ProtectedData kProtectedData = {cborProtectedData.encode()};
+
+ std::vector<uint8_t> eekChain;
+ std::vector<uint8_t> challenge;
+
+ // Set up mock, then call getSCsr
+ auto mockRpc = SharedRefBase::make<MockIRemotelyProvisionedComponent>();
+ EXPECT_CALL(*mockRpc, getHardwareInfo(NotNull())).WillOnce(Return(ByMove(ScopedAStatus::ok())));
+ EXPECT_CALL(*mockRpc,
+ generateCertificateRequest(false, // testMode
+ IsEmpty(), // keysToSign
+ _, // endpointEncryptionCertChain
+ _, // challenge
+ NotNull(), // deviceInfo
+ NotNull(), // protectedData
+ NotNull())) // _aidl_return
+ .WillOnce(DoAll(SaveArg<2>(&eekChain), //
+ SaveArg<3>(&challenge), //
+ SetArgPointee<4>(kVerifiedDeviceInfo), //
+ SetArgPointee<5>(kProtectedData), //
+ SetArgPointee<6>(kFakeMac), //
+ Return(ByMove(ScopedAStatus::ok())))); //
+
+ auto [csr, csrErrMsg] = getCsr("mock component name", mockRpc.get());
+ ASSERT_THAT(csr, NotNull()) << csrErrMsg.value_or("");
+ ASSERT_THAT(csr->asArray(), Pointee(Property(&Array::size, Eq(4))));
+
+ // Verify the input parameters that we received
+ auto [parsedEek, ignore1, eekParseError] = parse(eekChain);
+ ASSERT_THAT(parsedEek, NotNull()) << eekParseError;
+ EXPECT_THAT(parsedEek->asArray(), Pointee(Property(&Array::size, Gt(1))));
+ EXPECT_THAT(challenge, Property(&std::vector<uint8_t>::size, Eq(kChallengeSize)));
+
+ // Device info consists of (verified info, unverified info)
+ const Array* deviceInfoArray = csr->get(0)->asArray();
+ EXPECT_THAT(deviceInfoArray, Pointee(Property(&Array::size, 2)));
+
+ // Verified device info must match our mock value
+ const Map* actualVerifiedDeviceInfo = deviceInfoArray->get(0)->asMap();
+ EXPECT_THAT(actualVerifiedDeviceInfo, Pointee(Property(&Map::size, Eq(2))));
+ EXPECT_THAT(actualVerifiedDeviceInfo->get("product"), Pointee(Eq(Tstr("gShoe"))));
+ EXPECT_THAT(actualVerifiedDeviceInfo->get("version"), Pointee(Eq(Uint(2))));
+
+ // Empty unverified device info
+ const Map* actualUnverifiedDeviceInfo = deviceInfoArray->get(1)->asMap();
+ EXPECT_THAT(actualUnverifiedDeviceInfo, Pointee(Property(&Map::size, Eq(0))));
+
+ // Challenge must match the call to generateCertificateRequest
+ const Bstr* actualChallenge = csr->get(1)->asBstr();
+ EXPECT_THAT(actualChallenge, Pointee(Property(&Bstr::value, Eq(challenge))));
+
+ // Protected data must match the mock value
+ const Array* actualProtectedData = csr->get(2)->asArray();
+ EXPECT_THAT(actualProtectedData, Pointee(Eq(ByRef(cborProtectedData))));
+
+ // Ensure the maced public key matches the expected COSE_mac0
+ const Array* actualMacedKeys = csr->get(3)->asArray();
+ ASSERT_THAT(actualMacedKeys, Pointee(Property(&Array::size, Eq(4))));
+ ASSERT_THAT(actualMacedKeys->get(0)->asBstr(), NotNull());
+ auto [macProtectedParams, ignore2, macParamParseError] =
+ parse(actualMacedKeys->get(0)->asBstr());
+ ASSERT_THAT(macProtectedParams, NotNull()) << macParamParseError;
+ Map expectedMacProtectedParams;
+ expectedMacProtectedParams.add(1, 5);
+ EXPECT_THAT(macProtectedParams, Pointee(Eq(ByRef(expectedMacProtectedParams))));
+ EXPECT_THAT(actualMacedKeys->get(1)->asMap(), Pointee(Property(&Map::size, Eq(0))));
+ EXPECT_THAT(actualMacedKeys->get(2)->asNull(), NotNull());
+ EXPECT_THAT(actualMacedKeys->get(3)->asBstr(), Pointee(Eq(Bstr(kFakeMac))));
+}
diff --git a/provisioner/rkp_factory_extraction_tool.cpp b/provisioner/rkp_factory_extraction_tool.cpp
index 0f45531..ee8d851 100644
--- a/provisioner/rkp_factory_extraction_tool.cpp
+++ b/provisioner/rkp_factory_extraction_tool.cpp
@@ -14,9 +14,6 @@
* limitations under the License.
*/
-#include <string>
-#include <vector>
-
#include <aidl/android/hardware/security/keymint/IRemotelyProvisionedComponent.h>
#include <android/binder_manager.h>
#include <cppbor.h>
@@ -26,20 +23,17 @@
#include <remote_prov/remote_prov_utils.h>
#include <sys/random.h>
-using aidl::android::hardware::security::keymint::DeviceInfo;
+#include <string>
+#include <vector>
+
+#include "rkp_factory_extraction_lib.h"
+
using aidl::android::hardware::security::keymint::IRemotelyProvisionedComponent;
-using aidl::android::hardware::security::keymint::MacedPublicKey;
-using aidl::android::hardware::security::keymint::ProtectedData;
-using aidl::android::hardware::security::keymint::RpcHardwareInfo;
-using aidl::android::hardware::security::keymint::remote_prov::generateEekChain;
-using aidl::android::hardware::security::keymint::remote_prov::getProdEekChain;
using aidl::android::hardware::security::keymint::remote_prov::jsonEncodeCsrWithBuild;
using namespace cppbor;
using namespace cppcose;
-DEFINE_bool(test_mode, false, "If enabled, a fake EEK key/cert are used.");
-
DEFINE_string(output_format, "csr", "How to format the output. Defaults to 'csr'.");
namespace {
@@ -51,87 +45,6 @@
constexpr size_t kChallengeSize = 16;
-std::string toBase64(const std::vector<uint8_t>& buffer) {
- size_t base64Length;
- int rc = EVP_EncodedLength(&base64Length, buffer.size());
- if (!rc) {
- std::cerr << "Error getting base64 length. Size overflow?" << std::endl;
- exit(-1);
- }
-
- std::string base64(base64Length, ' ');
- rc = EVP_EncodeBlock(reinterpret_cast<uint8_t*>(base64.data()), buffer.data(), buffer.size());
- ++rc; // Account for NUL, which BoringSSL does not for some reason.
- if (rc != base64Length) {
- std::cerr << "Error writing base64. Expected " << base64Length
- << " bytes to be written, but " << rc << " bytes were actually written."
- << std::endl;
- exit(-1);
- }
- return base64;
-}
-
-std::vector<uint8_t> generateChallenge() {
- std::vector<uint8_t> challenge(kChallengeSize);
-
- ssize_t bytesRemaining = static_cast<ssize_t>(challenge.size());
- uint8_t* writePtr = challenge.data();
- while (bytesRemaining > 0) {
- int bytesRead = getrandom(writePtr, bytesRemaining, /*flags=*/0);
- if (bytesRead < 0) {
- if (errno == EINTR) {
- continue;
- } else {
- std::cerr << errno << ": " << strerror(errno) << std::endl;
- exit(-1);
- }
- }
- bytesRemaining -= bytesRead;
- writePtr += bytesRead;
- }
-
- return challenge;
-}
-
-Array composeCertificateRequest(const ProtectedData& protectedData,
- const DeviceInfo& verifiedDeviceInfo,
- const std::vector<uint8_t>& challenge,
- const std::vector<uint8_t>& keysToSignMac) {
- Array macedKeysToSign = Array()
- .add(std::vector<uint8_t>(0)) // empty protected headers as bstr
- .add(Map()) // empty unprotected headers
- .add(Null()) // nil for the payload
- .add(keysToSignMac); // MAC as returned from the HAL
-
- Array deviceInfo =
- Array().add(EncodedItem(verifiedDeviceInfo.deviceInfo)).add(Map()); // Empty device info
-
- Array certificateRequest = Array()
- .add(std::move(deviceInfo))
- .add(challenge)
- .add(EncodedItem(protectedData.protectedData))
- .add(std::move(macedKeysToSign));
- return certificateRequest;
-}
-
-std::vector<uint8_t> getEekChain(uint32_t curve) {
- if (FLAGS_test_mode) {
- const std::vector<uint8_t> kFakeEekId = {'f', 'a', 'k', 'e', 0};
- auto eekOrErr = generateEekChain(curve, 3 /* chainlength */, kFakeEekId);
- if (!eekOrErr) {
- std::cerr << "Failed to generate test EEK somehow: " << eekOrErr.message() << std::endl;
- exit(-1);
- }
- auto [eek, pubkey, privkey] = eekOrErr.moveValue();
- std::cout << "EEK raw keypair:" << std::endl;
- std::cout << " pub: " << toBase64(pubkey) << std::endl;
- std::cout << " priv: " << toBase64(privkey) << std::endl;
- return eek;
- }
-
- return getProdEekChain(curve);
-}
-
void writeOutput(const std::string instance_name, const Array& csr) {
if (FLAGS_output_format == kBinaryCsrOutput) {
auto bytes = csr.encode();
@@ -166,28 +79,14 @@
exit(-1);
}
- std::vector<uint8_t> keysToSignMac;
- std::vector<MacedPublicKey> emptyKeys;
- DeviceInfo verifiedDeviceInfo;
- ProtectedData protectedData;
- RpcHardwareInfo hwInfo;
- ::ndk::ScopedAStatus status = rkp_service->getHardwareInfo(&hwInfo);
- if (!status.isOk()) {
- std::cerr << "Failed to get hardware info for '" << fullName
- << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
+ auto [request, errMsg] = getCsr(name, rkp_service.get());
+ if (!request) {
+ std::cerr << "Unable to build CSR for '" << fullName << ": "
+ << errMsg.value_or("<Unknown Error>") << std::endl;
exit(-1);
}
- status = rkp_service->generateCertificateRequest(
- FLAGS_test_mode, emptyKeys, getEekChain(hwInfo.supportedEekCurve), challenge,
- &verifiedDeviceInfo, &protectedData, &keysToSignMac);
- if (!status.isOk()) {
- std::cerr << "Bundle extraction failed for '" << fullName
- << "'. Error code: " << status.getServiceSpecificError() << "." << std::endl;
- exit(-1);
- }
- auto request =
- composeCertificateRequest(protectedData, verifiedDeviceInfo, challenge, keysToSignMac);
- writeOutput(std::string(name), request);
+
+ writeOutput(std::string(name), *request);
}
} // namespace