Merge "Keystore 2.0: Fix racy super key management."
diff --git a/identity/Android.bp b/identity/Android.bp
index 7b0503a..790a731 100644
--- a/identity/Android.bp
+++ b/identity/Android.bp
@@ -27,6 +27,7 @@
defaults: [
"identity_defaults",
"keymint_use_latest_hal_aidl_ndk_shared",
+ "keymint_use_latest_hal_aidl_cpp_static",
],
srcs: [
diff --git a/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl b/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl
new file mode 100644
index 0000000..7d45e52
--- /dev/null
+++ b/keystore2/aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 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.
+ */
+
+package android.security.remoteprovisioning;
+
+import android.security.remoteprovisioning.RemotelyProvisionedKey;
+
+/**
+ * This is the interface providing access to remotely-provisioned attestation keys
+ * for an `IRemotelyProvisionedComponent`.
+ *
+ * @hide
+ */
+interface IRemotelyProvisionedKeyPool {
+
+ /**
+ * Fetches an attestation key for the given uid and `IRemotelyProvisionedComponent`, as
+ * identified by the given id.
+
+ * Callers require the keystore2::get_attestation_key permission.
+ *
+ * ## Error conditions
+ * `android.system.keystore2.ResponseCode::PERMISSION_DENIED` if the caller does not have the
+ * `keystore2::get_attestation_key` permission
+ *
+ * @param clientUid The client application for which an attestation key is needed.
+ *
+ * @param irpcId The unique identifier for the `IRemotelyProvisionedComponent` for which a key
+ * is requested. This id may be retrieved from a given component via the
+ * `IRemotelyProvisionedComponent::getHardwareInfo` function.
+ *
+ * @return A `RemotelyProvisionedKey` parcelable containing a key and certification chain for
+ * the given `IRemotelyProvisionedComponent`.
+ */
+ RemotelyProvisionedKey getAttestationKey(in int clientUid, in @utf8InCpp String irpcId);
+}
diff --git a/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl b/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl
new file mode 100644
index 0000000..ae21855
--- /dev/null
+++ b/keystore2/aidl/android/security/remoteprovisioning/RemotelyProvisionedKey.aidl
@@ -0,0 +1,42 @@
+/*
+ * 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.
+ */
+
+package android.security.remoteprovisioning;
+
+/**
+ * A `RemotelyProvisionedKey` holds an attestation key and the corresponding remotely provisioned
+ * certificate chain.
+ *
+ * @hide
+ */
+@RustDerive(Eq=true, PartialEq=true)
+parcelable RemotelyProvisionedKey {
+ /**
+ * The remotely-provisioned key that may be used to sign attestations. The format of this key
+ * is opaque, and need only be understood by the IRemotelyProvisionedComponent that generated
+ * it.
+ *
+ * Any private key material contained within this blob must be encrypted.
+ */
+ byte[] keyBlob;
+
+ /**
+ * Sequence of DER-encoded X.509 certificates that make up the attestation key's certificate
+ * chain. This is the binary encoding for a chain that is supported by Java's
+ * CertificateFactory.generateCertificates API.
+ */
+ byte[] encodedCertChain;
+}
diff --git a/keystore2/src/database.rs b/keystore2/src/database.rs
index 0520b7c..e68b0fd 100644
--- a/keystore2/src/database.rs
+++ b/keystore2/src/database.rs
@@ -3207,7 +3207,7 @@
}
#[cfg(test)]
-mod tests {
+pub mod tests {
use super::*;
use crate::key_parameter::{
@@ -3237,7 +3237,7 @@
#[cfg(disabled)]
use std::time::Instant;
- fn new_test_db() -> Result<KeystoreDB> {
+ pub fn new_test_db() -> Result<KeystoreDB> {
let conn = KeystoreDB::make_connection("file::memory:")?;
let mut db = KeystoreDB { conn, gc: None, perboot: Arc::new(perboot::PerbootDB::new()) };
diff --git a/keystore2/src/error.rs b/keystore2/src/error.rs
index 42dd3d2..f34c5da 100644
--- a/keystore2/src/error.rs
+++ b/keystore2/src/error.rs
@@ -67,10 +67,15 @@
Error::Rc(ResponseCode::SYSTEM_ERROR)
}
- /// Short hand for `Error::Rc(ResponseCode::PERMISSION_DENIED`
+ /// Short hand for `Error::Rc(ResponseCode::PERMISSION_DENIED)`
pub fn perm() -> Self {
Error::Rc(ResponseCode::PERMISSION_DENIED)
}
+
+ /// Short hand for `Error::Rc(ResponseCode::OUT_OF_KEYS)`
+ pub fn out_of_keys() -> Self {
+ Error::Rc(ResponseCode::OUT_OF_KEYS)
+ }
}
/// Helper function to map the binder status we get from calls into KeyMint
diff --git a/keystore2/src/permission.rs b/keystore2/src/permission.rs
index e6d61b0..1e6f10a 100644
--- a/keystore2/src/permission.rs
+++ b/keystore2/src/permission.rs
@@ -149,6 +149,9 @@
/// introduced for migrating keys when an app leaves a sharedUserId.
#[selinux(name = migrate_any_key)]
MigrateAnyKey,
+ /// Checked on calls to IRemotelyProvisionedKeyPool::getAttestationKey
+ #[selinux(name = get_attestation_key)]
+ GetAttestationKey,
}
);
diff --git a/keystore2/src/remote_provisioning.rs b/keystore2/src/remote_provisioning.rs
index 132ffbe..fadd252 100644
--- a/keystore2/src/remote_provisioning.rs
+++ b/keystore2/src/remote_provisioning.rs
@@ -30,11 +30,13 @@
};
use android_security_remoteprovisioning::aidl::android::security::remoteprovisioning::{
AttestationPoolStatus::AttestationPoolStatus, IRemoteProvisioning::BnRemoteProvisioning,
- IRemoteProvisioning::IRemoteProvisioning, ImplInfo::ImplInfo,
+ IRemoteProvisioning::IRemoteProvisioning,
+ IRemotelyProvisionedKeyPool::IRemotelyProvisionedKeyPool, ImplInfo::ImplInfo,
+ RemotelyProvisionedKey::RemotelyProvisionedKey,
};
use android_security_remoteprovisioning::binder::{BinderFeatures, Strong};
use android_system_keystore2::aidl::android::system::keystore2::{
- Domain::Domain, KeyDescriptor::KeyDescriptor,
+ Domain::Domain, KeyDescriptor::KeyDescriptor, ResponseCode::ResponseCode,
};
use anyhow::{Context, Result};
use keystore2_crypto::parse_subject_from_certificate;
@@ -46,7 +48,8 @@
use crate::error::{self, map_or_log_err, map_rem_prov_error, Error};
use crate::globals::{get_keymint_device, get_remotely_provisioned_component, DB};
use crate::metrics_store::log_rkp_error_stats;
-use crate::utils::watchdog as wd;
+use crate::permission::KeystorePerm;
+use crate::utils::{check_keystore_permission, watchdog as wd};
use android_security_metrics::aidl::android::security::metrics::RkpError::RkpError as MetricsRkpError;
/// Contains helper functions to check if remote provisioning is enabled on the system and, if so,
@@ -90,70 +93,6 @@
Ok(pool_status.total != 0)
}
- /// Fetches a remote provisioning attestation key and certificate chain inside of the
- /// returned `CertificateChain` struct if one exists for the given caller_uid. If one has not
- /// been assigned, this function will assign it. If there are no signed attestation keys
- /// available to be assigned, it will return the ResponseCode `OUT_OF_KEYS`
- fn get_rem_prov_attest_key(
- &self,
- key: &KeyDescriptor,
- caller_uid: u32,
- db: &mut KeystoreDB,
- ) -> Result<Option<CertificateChain>> {
- match key.domain {
- Domain::APP => {
- // Attempt to get an Attestation Key once. If it fails, then the app doesn't
- // have a valid chain assigned to it. The helper function will return None after
- // attempting to assign a key. An error will be thrown if the pool is simply out
- // of usable keys. Then another attempt to fetch the just-assigned key will be
- // made. If this fails too, something is very wrong.
- self.get_rem_prov_attest_key_helper(key, caller_uid, db)
- .context("In get_rem_prov_attest_key: Failed to get a key")?
- .map_or_else(
- || self.get_rem_prov_attest_key_helper(key, caller_uid, db),
- |v| Ok(Some(v)),
- )
- .context(concat!(
- "In get_rem_prov_attest_key: Failed to get a key after",
- "attempting to assign one."
- ))?
- .map_or_else(
- || {
- Err(Error::sys()).context(concat!(
- "In get_rem_prov_attest_key: Attempted to assign a ",
- "key and failed silently. Something is very wrong."
- ))
- },
- |cert_chain| Ok(Some(cert_chain)),
- )
- }
- _ => Ok(None),
- }
- }
-
- /// Returns None if an AttestationKey fails to be assigned. Errors if no keys are available.
- fn get_rem_prov_attest_key_helper(
- &self,
- key: &KeyDescriptor,
- caller_uid: u32,
- db: &mut KeystoreDB,
- ) -> Result<Option<CertificateChain>> {
- let cert_chain = db
- .retrieve_attestation_key_and_cert_chain(key.domain, caller_uid as i64, &self.km_uuid)
- .context("In get_rem_prov_attest_key_helper: Failed to retrieve a key + cert chain")?;
- match cert_chain {
- Some(cert_chain) => Ok(Some(cert_chain)),
- // Either this app needs to be assigned a key, or the pool is empty. An error will
- // be thrown if there is no key available to assign. This will indicate that the app
- // should be nudged to provision more keys so keystore can retry.
- None => {
- db.assign_attestation_key(key.domain, caller_uid as i64, &self.km_uuid)
- .context("In get_rem_prov_attest_key_helper: Failed to assign a key")?;
- Ok(None)
- }
- }
- }
-
fn is_asymmetric_key(&self, params: &[KeyParameter]) -> bool {
params.iter().any(|kp| {
matches!(
@@ -189,7 +128,7 @@
// and therefore will not be attested.
Ok(None)
} else {
- match self.get_rem_prov_attest_key(key, caller_uid, db) {
+ match get_rem_prov_attest_key(key.domain, caller_uid, db, &self.km_uuid) {
Err(e) => {
log::error!(
concat!(
@@ -233,9 +172,9 @@
fn get_dev_by_sec_level(
&self,
sec_level: &SecurityLevel,
- ) -> Result<Strong<dyn IRemotelyProvisionedComponent>> {
+ ) -> Result<&dyn IRemotelyProvisionedComponent> {
if let Some(dev) = self.device_by_sec_level.get(sec_level) {
- Ok(dev.clone())
+ Ok(dev.as_ref())
} else {
Err(error::Error::sys()).context(concat!(
"In get_dev_by_sec_level: Remote instance for requested security level",
@@ -244,6 +183,22 @@
}
}
+ fn get_dev_by_unique_id(
+ &self,
+ unique_id: &str,
+ ) -> Result<(SecurityLevel, &dyn IRemotelyProvisionedComponent)> {
+ for (sec_level, dev) in &self.device_by_sec_level {
+ if dev.getHardwareInfo()?.uniqueId == Some(unique_id.to_string()) {
+ return Ok((*sec_level, dev.as_ref()));
+ }
+ }
+
+ Err(error::Error::sys()).context(format!(
+ "In get_dev_by_unique_id: Instance for requested unique id '{}' not found",
+ unique_id
+ ))
+ }
+
/// Creates a new instance of the remote provisioning service
pub fn new_native_binder() -> Result<Strong<dyn IRemoteProvisioning>> {
let mut result: Self = Default::default();
@@ -346,23 +301,21 @@
/// here.
pub fn provision_cert_chain(
&self,
+ db: &mut KeystoreDB,
public_key: &[u8],
batch_cert: &[u8],
certs: &[u8],
expiration_date: i64,
sec_level: SecurityLevel,
) -> Result<()> {
- DB.with::<_, Result<()>>(|db| {
- let mut db = db.borrow_mut();
- let (_, _, uuid) = get_keymint_device(&sec_level)?;
- db.store_signed_attestation_certificate_chain(
- public_key,
- batch_cert,
- certs, /* DER encoded certificate chain */
- expiration_date,
- &uuid,
- )
- })
+ let (_, _, uuid) = get_keymint_device(&sec_level)?;
+ db.store_signed_attestation_certificate_chain(
+ public_key,
+ batch_cert,
+ certs, /* DER encoded certificate chain */
+ expiration_date,
+ &uuid,
+ )
}
fn parse_cose_mac0_for_coords(data: &[u8]) -> Result<Vec<u8>> {
@@ -429,19 +382,25 @@
/// `is_test_mode` indicates whether or not the returned public key should be marked as being
/// for testing in order to differentiate them from private keys. If the call is successful,
/// the key pair is then added to the database.
- pub fn generate_key_pair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> Result<()> {
+ pub fn generate_key_pair(
+ &self,
+ db: &mut KeystoreDB,
+ is_test_mode: bool,
+ sec_level: SecurityLevel,
+ ) -> Result<()> {
let (_, _, uuid) = get_keymint_device(&sec_level)?;
- let dev = self.get_dev_by_sec_level(&sec_level)?;
+ let dev = self.get_dev_by_sec_level(&sec_level).context(format!(
+ "In generate_key_pair: Failed to get device for security level {:?}",
+ sec_level
+ ))?;
let mut maced_key = MacedPublicKey { macedKey: Vec::new() };
let priv_key =
map_rem_prov_error(dev.generateEcdsaP256KeyPair(is_test_mode, &mut maced_key))
.context("In generate_key_pair: Failed to generated ECDSA keypair.")?;
let raw_key = Self::parse_cose_mac0_for_coords(&maced_key.macedKey)
.context("In generate_key_pair: Failed to parse raw key")?;
- DB.with::<_, Result<()>>(|db| {
- let mut db = db.borrow_mut();
- db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid)
- })
+ db.create_attestation_key_entry(&maced_key.macedKey, &raw_key, &priv_key, &uuid)
+ .context("In generate_key_pair: Failed to insert attestation key entry")
}
/// Checks the security level of each available IRemotelyProvisionedComponent hal and returns
@@ -462,6 +421,35 @@
db.delete_all_attestation_keys()
})
}
+
+ /// Fetches a remotely provisioned certificate chain and key for the given client uid that
+ /// was provisioned using the IRemotelyProvisionedComponent with the given id. The same key
+ /// will be returned for a given caller_uid on every request. If there are no attestation keys
+ /// available, `OUT_OF_KEYS` is returned.
+ fn get_attestation_key(
+ &self,
+ db: &mut KeystoreDB,
+ caller_uid: i32,
+ irpc_id: &str,
+ ) -> Result<RemotelyProvisionedKey> {
+ log::info!("get_attestation_key(self, {}, {}", caller_uid, irpc_id);
+
+ let (sec_level, _) = self.get_dev_by_unique_id(irpc_id)?;
+ let (_, _, km_uuid) = get_keymint_device(&sec_level)?;
+
+ let cert_chain = get_rem_prov_attest_key(Domain::APP, caller_uid as u32, db, &km_uuid)
+ .context("In get_attestation_key")?;
+ match cert_chain {
+ Some(chain) => Ok(RemotelyProvisionedKey {
+ keyBlob: chain.private_key.to_vec(),
+ encodedCertChain: chain.cert_chain,
+ }),
+ // It should be impossible to get `None`, but handle it just in case as a
+ // precaution against future behavioral changes in `get_rem_prov_attest_key`.
+ None => Err(error::Error::Rc(ResponseCode::OUT_OF_KEYS))
+ .context("In get_attestation_key: No available attestation keys"),
+ }
+ }
}
/// Populates the AttestationPoolStatus parcelable with information about how many
@@ -480,6 +468,70 @@
})
}
+/// Fetches a remote provisioning attestation key and certificate chain inside of the
+/// returned `CertificateChain` struct if one exists for the given caller_uid. If one has not
+/// been assigned, this function will assign it. If there are no signed attestation keys
+/// available to be assigned, it will return the ResponseCode `OUT_OF_KEYS`
+fn get_rem_prov_attest_key(
+ domain: Domain,
+ caller_uid: u32,
+ db: &mut KeystoreDB,
+ km_uuid: &Uuid,
+) -> Result<Option<CertificateChain>> {
+ match domain {
+ Domain::APP => {
+ // Attempt to get an Attestation Key once. If it fails, then the app doesn't
+ // have a valid chain assigned to it. The helper function will return None after
+ // attempting to assign a key. An error will be thrown if the pool is simply out
+ // of usable keys. Then another attempt to fetch the just-assigned key will be
+ // made. If this fails too, something is very wrong.
+ get_rem_prov_attest_key_helper(domain, caller_uid, db, km_uuid)
+ .context("In get_rem_prov_attest_key: Failed to get a key")?
+ .map_or_else(
+ || get_rem_prov_attest_key_helper(domain, caller_uid, db, km_uuid),
+ |v| Ok(Some(v)),
+ )
+ .context(concat!(
+ "In get_rem_prov_attest_key: Failed to get a key after",
+ "attempting to assign one."
+ ))?
+ .map_or_else(
+ || {
+ Err(Error::sys()).context(concat!(
+ "In get_rem_prov_attest_key: Attempted to assign a ",
+ "key and failed silently. Something is very wrong."
+ ))
+ },
+ |cert_chain| Ok(Some(cert_chain)),
+ )
+ }
+ _ => Ok(None),
+ }
+}
+
+/// Returns None if an AttestationKey fails to be assigned. Errors if no keys are available.
+fn get_rem_prov_attest_key_helper(
+ domain: Domain,
+ caller_uid: u32,
+ db: &mut KeystoreDB,
+ km_uuid: &Uuid,
+) -> Result<Option<CertificateChain>> {
+ let cert_chain = db
+ .retrieve_attestation_key_and_cert_chain(domain, caller_uid as i64, km_uuid)
+ .context("In get_rem_prov_attest_key_helper: Failed to retrieve a key + cert chain")?;
+ match cert_chain {
+ Some(cert_chain) => Ok(Some(cert_chain)),
+ // Either this app needs to be assigned a key, or the pool is empty. An error will
+ // be thrown if there is no key available to assign. This will indicate that the app
+ // should be nudged to provision more keys so keystore can retry.
+ None => {
+ db.assign_attestation_key(domain, caller_uid as i64, km_uuid)
+ .context("In get_rem_prov_attest_key_helper: Failed to assign a key")?;
+ Ok(None)
+ }
+ }
+}
+
impl binder::Interface for RemoteProvisioningService {}
// Implementation of IRemoteProvisioning. See AIDL spec at
@@ -528,15 +580,29 @@
sec_level: SecurityLevel,
) -> binder::Result<()> {
let _wp = wd::watch_millis("IRemoteProvisioning::provisionCertChain", 500);
- map_or_log_err(
- self.provision_cert_chain(public_key, batch_cert, certs, expiration_date, sec_level),
- Ok,
- )
+ DB.with::<_, binder::Result<()>>(|db| {
+ map_or_log_err(
+ self.provision_cert_chain(
+ &mut db.borrow_mut(),
+ public_key,
+ batch_cert,
+ certs,
+ expiration_date,
+ sec_level,
+ ),
+ Ok,
+ )
+ })
}
fn generateKeyPair(&self, is_test_mode: bool, sec_level: SecurityLevel) -> binder::Result<()> {
let _wp = wd::watch_millis("IRemoteProvisioning::generateKeyPair", 500);
- map_or_log_err(self.generate_key_pair(is_test_mode, sec_level), Ok)
+ DB.with::<_, binder::Result<()>>(|db| {
+ map_or_log_err(
+ self.generate_key_pair(&mut db.borrow_mut(), is_test_mode, sec_level),
+ Ok,
+ )
+ })
}
fn getImplementationInfo(&self) -> binder::Result<Vec<ImplInfo>> {
@@ -550,11 +616,126 @@
}
}
+// Implementation of IRemotelyProvisionedKeyPool. See AIDL spec at
+// :aidl/android/security/remoteprovisioning/IRemotelyProvisionedKeyPool.aidl
+impl IRemotelyProvisionedKeyPool for RemoteProvisioningService {
+ fn getAttestationKey(
+ &self,
+ caller_uid: i32,
+ irpc_id: &str,
+ ) -> binder::Result<RemotelyProvisionedKey> {
+ let _wp = wd::watch_millis("IRemotelyProvisionedKeyPool::getAttestationKey", 500);
+ map_or_log_err(check_keystore_permission(KeystorePerm::GetAttestationKey), Ok)?;
+ DB.with::<_, binder::Result<RemotelyProvisionedKey>>(|db| {
+ map_or_log_err(self.get_attestation_key(&mut db.borrow_mut(), caller_uid, irpc_id), Ok)
+ })
+ }
+}
+
#[cfg(test)]
mod tests {
use super::*;
use serde_cbor::Value;
use std::collections::BTreeMap;
+ use std::sync::{Arc, Mutex};
+ use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
+ RpcHardwareInfo::RpcHardwareInfo,
+ };
+
+ #[derive(Default)]
+ struct MockRemotelyProvisionedComponentValues {
+ hw_info: RpcHardwareInfo,
+ private_key: Vec<u8>,
+ maced_public_key: Vec<u8>,
+ }
+
+ // binder::Interface requires the Send trait, so we have to use a Mutex even though the test
+ // is single threaded.
+ #[derive(Default)]
+ struct MockRemotelyProvisionedComponent(Arc<Mutex<MockRemotelyProvisionedComponentValues>>);
+
+ impl binder::Interface for MockRemotelyProvisionedComponent {}
+
+ impl IRemotelyProvisionedComponent for MockRemotelyProvisionedComponent {
+ fn getHardwareInfo(&self) -> binder::Result<RpcHardwareInfo> {
+ Ok(self.0.lock().unwrap().hw_info.clone())
+ }
+
+ fn generateEcdsaP256KeyPair(
+ &self,
+ test_mode: bool,
+ maced_public_key: &mut MacedPublicKey,
+ ) -> binder::Result<Vec<u8>> {
+ assert!(test_mode);
+ maced_public_key.macedKey = self.0.lock().unwrap().maced_public_key.clone();
+ Ok(self.0.lock().unwrap().private_key.clone())
+ }
+
+ fn generateCertificateRequest(
+ &self,
+ _test_mode: bool,
+ _keys_to_sign: &[MacedPublicKey],
+ _eek: &[u8],
+ _challenge: &[u8],
+ _device_info: &mut DeviceInfo,
+ _protected_data: &mut ProtectedData,
+ ) -> binder::Result<Vec<u8>> {
+ Err(binder::StatusCode::INVALID_OPERATION.into())
+ }
+ }
+
+ // Hard coded cert that can be parsed -- the content doesn't matter for testing, only that it's valid.
+ fn get_fake_cert() -> Vec<u8> {
+ vec![
+ 0x30, 0x82, 0x01, 0xbb, 0x30, 0x82, 0x01, 0x61, 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02,
+ 0x14, 0x3a, 0xd5, 0x67, 0xce, 0xfe, 0x93, 0xe1, 0xea, 0xb7, 0xe4, 0xbf, 0x64, 0x19,
+ 0xa4, 0x11, 0xe1, 0x87, 0x40, 0x20, 0x37, 0x30, 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48,
+ 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55,
+ 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04,
+ 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65, 0x31,
+ 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f, 0x67,
+ 0x6c, 0x65, 0x30, 0x1e, 0x17, 0x0d, 0x32, 0x31, 0x31, 0x32, 0x31, 0x30, 0x32, 0x32,
+ 0x30, 0x38, 0x35, 0x32, 0x5a, 0x17, 0x0d, 0x34, 0x39, 0x30, 0x34, 0x32, 0x36, 0x32,
+ 0x32, 0x30, 0x38, 0x35, 0x32, 0x5a, 0x30, 0x33, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03,
+ 0x55, 0x04, 0x06, 0x13, 0x02, 0x55, 0x54, 0x31, 0x13, 0x30, 0x11, 0x06, 0x03, 0x55,
+ 0x04, 0x08, 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, 0x61, 0x74, 0x65,
+ 0x31, 0x0f, 0x30, 0x0d, 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x06, 0x47, 0x6f, 0x6f,
+ 0x67, 0x6c, 0x65, 0x30, 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, 0xce, 0x3d,
+ 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42,
+ 0x00, 0x04, 0x1e, 0xac, 0x0c, 0xe0, 0x0d, 0xc5, 0x25, 0x84, 0x1b, 0xd2, 0x77, 0x2d,
+ 0xe7, 0xba, 0xf1, 0xde, 0xa7, 0xf6, 0x39, 0x7f, 0x38, 0x91, 0xbf, 0xa4, 0x58, 0xf5,
+ 0x62, 0x6b, 0xce, 0x06, 0xcf, 0xb9, 0x73, 0x91, 0x0d, 0x8a, 0x60, 0xa0, 0xc6, 0xa2,
+ 0x22, 0xe6, 0x51, 0x2e, 0x58, 0xd6, 0x43, 0x02, 0x80, 0x43, 0x44, 0x29, 0x38, 0x9a,
+ 0x99, 0xf3, 0xa4, 0xdd, 0xd0, 0xb4, 0x6f, 0x8b, 0x44, 0x2d, 0xa3, 0x53, 0x30, 0x51,
+ 0x30, 0x1d, 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, 0x14, 0xdb, 0x13, 0x68,
+ 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe, 0x42, 0x3c, 0xd9, 0x3f, 0x1a,
+ 0x33, 0xe9, 0xaa, 0x30, 0x1f, 0x06, 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16,
+ 0x80, 0x14, 0xdb, 0x13, 0x68, 0xe0, 0x0e, 0x47, 0x10, 0xf8, 0xcb, 0x88, 0x83, 0xfe,
+ 0x42, 0x3c, 0xd9, 0x3f, 0x1a, 0x33, 0xe9, 0xaa, 0x30, 0x0f, 0x06, 0x03, 0x55, 0x1d,
+ 0x13, 0x01, 0x01, 0xff, 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, 0x0a, 0x06,
+ 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45,
+ 0x02, 0x20, 0x10, 0xdf, 0x40, 0xc3, 0x20, 0x54, 0x36, 0xb5, 0xc9, 0x3c, 0x70, 0xe3,
+ 0x55, 0x37, 0xd2, 0x04, 0x51, 0xeb, 0x0f, 0x18, 0x83, 0xd0, 0x58, 0xa1, 0x08, 0x77,
+ 0x8d, 0x4d, 0xa4, 0x20, 0xee, 0x33, 0x02, 0x21, 0x00, 0x8d, 0xe3, 0xa6, 0x6c, 0x0d,
+ 0x86, 0x25, 0xdc, 0x59, 0x0d, 0x21, 0x43, 0x22, 0x3a, 0xb9, 0xa1, 0x73, 0x28, 0xc9,
+ 0x16, 0x9e, 0x91, 0x15, 0xc4, 0xc3, 0xd7, 0xeb, 0xe5, 0xce, 0xdc, 0x1c, 0x1b,
+ ]
+ }
+
+ // Generate a fake COSE_Mac0 with a key that's just `byte` repeated
+ fn generate_maced_pubkey(byte: u8) -> Vec<u8> {
+ vec![
+ 0x84, 0x43, 0xA1, 0x01, 0x05, 0xA0, 0x58, 0x4D, 0xA5, 0x01, 0x02, 0x03, 0x26, 0x20,
+ 0x01, 0x21, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, 0x22, 0x58, 0x20, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, 0x58, 0x20, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte, byte,
+ byte, byte, byte, byte, byte, byte, byte,
+ ]
+ }
#[test]
fn test_parse_cose_mac0_for_coords_raw_bytes() -> Result<()> {
@@ -653,4 +834,178 @@
assert!(extracted_payload.is_err());
Ok(())
}
+
+ #[test]
+ #[ignore] // b/215746308
+ fn test_get_attestation_key_no_keys_provisioned() {
+ let mut db = crate::database::tests::new_test_db().unwrap();
+ let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default();
+ mock_rpc.0.lock().unwrap().hw_info.uniqueId = Some(String::from("mallory"));
+
+ let mut service: RemoteProvisioningService = Default::default();
+ service
+ .device_by_sec_level
+ .insert(SecurityLevel::TRUSTED_ENVIRONMENT, Strong::new(mock_rpc));
+
+ assert_eq!(
+ service
+ .get_attestation_key(&mut db, 0, "mallory")
+ .unwrap_err()
+ .downcast::<error::Error>()
+ .unwrap(),
+ error::Error::Rc(ResponseCode::OUT_OF_KEYS)
+ );
+ }
+
+ #[test]
+ #[ignore] // b/215746308
+ fn test_get_attestation_key() {
+ let mut db = crate::database::tests::new_test_db().unwrap();
+ let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT;
+ let irpc_id = "paul";
+ let caller_uid = 0;
+
+ let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default();
+ let mock_values = mock_rpc.0.clone();
+ let mut service: RemoteProvisioningService = Default::default();
+ service.device_by_sec_level.insert(sec_level, Strong::new(mock_rpc));
+
+ mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id));
+ mock_values.lock().unwrap().private_key = vec![8, 6, 7, 5, 3, 0, 9];
+ mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x11);
+ service.generate_key_pair(&mut db, true, sec_level).unwrap();
+
+ let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords(
+ mock_values.lock().unwrap().maced_public_key.as_slice(),
+ )
+ .unwrap();
+ let batch_cert = get_fake_cert();
+ let certs = &[5, 6, 7, 8];
+ assert!(service
+ .provision_cert_chain(
+ &mut db,
+ public_key.as_slice(),
+ batch_cert.as_slice(),
+ certs,
+ 0,
+ sec_level
+ )
+ .is_ok());
+
+ // ensure we got the key we expected
+ let first_key = service
+ .get_attestation_key(&mut db, caller_uid, irpc_id)
+ .context("get first key")
+ .unwrap();
+ assert_eq!(first_key.keyBlob, mock_values.lock().unwrap().private_key);
+ assert_eq!(first_key.encodedCertChain, certs);
+
+ // ensure that multiple calls get the same key
+ assert_eq!(
+ first_key,
+ service
+ .get_attestation_key(&mut db, caller_uid, irpc_id)
+ .context("get second key")
+ .unwrap()
+ );
+
+ // no more keys for new clients
+ assert_eq!(
+ service
+ .get_attestation_key(&mut db, caller_uid + 1, irpc_id)
+ .unwrap_err()
+ .downcast::<error::Error>()
+ .unwrap(),
+ error::Error::Rc(ResponseCode::OUT_OF_KEYS)
+ );
+ }
+
+ #[test]
+ #[ignore] // b/215746308
+ fn test_get_attestation_key_gets_different_key_for_different_client() {
+ let mut db = crate::database::tests::new_test_db().unwrap();
+ let sec_level = SecurityLevel::TRUSTED_ENVIRONMENT;
+ let irpc_id = "ringo";
+ let first_caller = 0;
+ let second_caller = first_caller + 1;
+
+ let mock_rpc = Box::<MockRemotelyProvisionedComponent>::default();
+ let mock_values = mock_rpc.0.clone();
+ let mut service: RemoteProvisioningService = Default::default();
+ service.device_by_sec_level.insert(sec_level, Strong::new(mock_rpc));
+
+ // generate two distinct keys and provision them with certs
+ mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id));
+ mock_values.lock().unwrap().private_key = vec![3, 1, 4, 1, 5];
+ mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x11);
+ assert!(service.generate_key_pair(&mut db, true, sec_level).is_ok());
+ let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords(
+ mock_values.lock().unwrap().maced_public_key.as_slice(),
+ )
+ .unwrap();
+ assert!(service
+ .provision_cert_chain(
+ &mut db,
+ public_key.as_slice(),
+ get_fake_cert().as_slice(),
+ &[1],
+ 0,
+ sec_level
+ )
+ .is_ok());
+
+ mock_values.lock().unwrap().hw_info.uniqueId = Some(String::from(irpc_id));
+ mock_values.lock().unwrap().private_key = vec![9, 0, 2, 1, 0];
+ mock_values.lock().unwrap().maced_public_key = generate_maced_pubkey(0x22);
+ assert!(service.generate_key_pair(&mut db, true, sec_level).is_ok());
+ let public_key = RemoteProvisioningService::parse_cose_mac0_for_coords(
+ mock_values.lock().unwrap().maced_public_key.as_slice(),
+ )
+ .unwrap();
+ assert!(service
+ .provision_cert_chain(
+ &mut db,
+ public_key.as_slice(),
+ get_fake_cert().as_slice(),
+ &[2],
+ 0,
+ sec_level
+ )
+ .is_ok());
+
+ // make sure each caller gets a distinct key
+ assert_ne!(
+ service
+ .get_attestation_key(&mut db, first_caller, irpc_id)
+ .context("get first key")
+ .unwrap(),
+ service
+ .get_attestation_key(&mut db, second_caller, irpc_id)
+ .context("get second key")
+ .unwrap()
+ );
+
+ // repeated calls should return the same key for a given caller
+ assert_eq!(
+ service
+ .get_attestation_key(&mut db, first_caller, irpc_id)
+ .context("first caller a")
+ .unwrap(),
+ service
+ .get_attestation_key(&mut db, first_caller, irpc_id)
+ .context("first caller b")
+ .unwrap(),
+ );
+
+ assert_eq!(
+ service
+ .get_attestation_key(&mut db, second_caller, irpc_id)
+ .context("second caller a")
+ .unwrap(),
+ service
+ .get_attestation_key(&mut db, second_caller, irpc_id)
+ .context("second caller b")
+ .unwrap()
+ );
+ }
}