• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 // Copyright 2020, The Android Open Source Project
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 //     http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS,
11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 // See the License for the specific language governing permissions and
13 // limitations under the License.
14 
15 //! This module implements utility functions used by the Keystore 2.0 service
16 //! implementation.
17 
18 use crate::error::{map_binder_status, map_km_error, Error, ErrorCode};
19 use crate::key_parameter::KeyParameter;
20 use crate::ks_err;
21 use crate::permission;
22 use crate::permission::{KeyPerm, KeyPermSet, KeystorePerm};
23 use crate::{
24     database::{KeyType, KeystoreDB},
25     globals::LEGACY_IMPORTER,
26 };
27 use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
28     IKeyMintDevice::IKeyMintDevice, KeyCharacteristics::KeyCharacteristics,
29     KeyParameter::KeyParameter as KmKeyParameter, Tag::Tag,
30 };
31 use android_os_permissions_aidl::aidl::android::os::IPermissionController;
32 use android_security_apc::aidl::android::security::apc::{
33     IProtectedConfirmation::{FLAG_UI_OPTION_INVERTED, FLAG_UI_OPTION_MAGNIFIED},
34     ResponseCode::ResponseCode as ApcResponseCode,
35 };
36 use android_system_keystore2::aidl::android::system::keystore2::{
37     Authorization::Authorization, Domain::Domain, KeyDescriptor::KeyDescriptor,
38 };
39 use anyhow::{Context, Result};
40 use binder::{Strong, ThreadState};
41 use keystore2_apc_compat::{
42     ApcCompatUiOptions, APC_COMPAT_ERROR_ABORTED, APC_COMPAT_ERROR_CANCELLED,
43     APC_COMPAT_ERROR_IGNORED, APC_COMPAT_ERROR_OK, APC_COMPAT_ERROR_OPERATION_PENDING,
44     APC_COMPAT_ERROR_SYSTEM_ERROR,
45 };
46 use keystore2_crypto::{aes_gcm_decrypt, aes_gcm_encrypt, ZVec};
47 use std::iter::IntoIterator;
48 
49 /// This function uses its namesake in the permission module and in
50 /// combination with with_calling_sid from the binder crate to check
51 /// if the caller has the given keystore permission.
check_keystore_permission(perm: KeystorePerm) -> anyhow::Result<()>52 pub fn check_keystore_permission(perm: KeystorePerm) -> anyhow::Result<()> {
53     ThreadState::with_calling_sid(|calling_sid| {
54         permission::check_keystore_permission(
55             calling_sid
56                 .ok_or_else(Error::sys)
57                 .context(ks_err!("Cannot check permission without calling_sid."))?,
58             perm,
59         )
60     })
61 }
62 
63 /// This function uses its namesake in the permission module and in
64 /// combination with with_calling_sid from the binder crate to check
65 /// if the caller has the given grant permission.
check_grant_permission(access_vec: KeyPermSet, key: &KeyDescriptor) -> anyhow::Result<()>66 pub fn check_grant_permission(access_vec: KeyPermSet, key: &KeyDescriptor) -> anyhow::Result<()> {
67     ThreadState::with_calling_sid(|calling_sid| {
68         permission::check_grant_permission(
69             calling_sid
70                 .ok_or_else(Error::sys)
71                 .context(ks_err!("Cannot check permission without calling_sid."))?,
72             access_vec,
73             key,
74         )
75     })
76 }
77 
78 /// This function uses its namesake in the permission module and in
79 /// combination with with_calling_sid from the binder crate to check
80 /// if the caller has the given key permission.
check_key_permission( perm: KeyPerm, key: &KeyDescriptor, access_vector: &Option<KeyPermSet>, ) -> anyhow::Result<()>81 pub fn check_key_permission(
82     perm: KeyPerm,
83     key: &KeyDescriptor,
84     access_vector: &Option<KeyPermSet>,
85 ) -> anyhow::Result<()> {
86     ThreadState::with_calling_sid(|calling_sid| {
87         permission::check_key_permission(
88             ThreadState::get_calling_uid(),
89             calling_sid
90                 .ok_or_else(Error::sys)
91                 .context(ks_err!("Cannot check permission without calling_sid."))?,
92             perm,
93             key,
94             access_vector,
95         )
96     })
97 }
98 
99 /// This function checks whether a given tag corresponds to the access of device identifiers.
is_device_id_attestation_tag(tag: Tag) -> bool100 pub fn is_device_id_attestation_tag(tag: Tag) -> bool {
101     matches!(
102         tag,
103         Tag::ATTESTATION_ID_IMEI
104             | Tag::ATTESTATION_ID_MEID
105             | Tag::ATTESTATION_ID_SERIAL
106             | Tag::DEVICE_UNIQUE_ATTESTATION
107             | Tag::ATTESTATION_ID_SECOND_IMEI
108     )
109 }
110 
111 /// This function checks whether the calling app has the Android permissions needed to attest device
112 /// identifiers. It throws an error if the permissions cannot be verified or if the caller doesn't
113 /// have the right permissions. Otherwise it returns silently.
check_device_attestation_permissions() -> anyhow::Result<()>114 pub fn check_device_attestation_permissions() -> anyhow::Result<()> {
115     check_android_permission("android.permission.READ_PRIVILEGED_PHONE_STATE")
116 }
117 
118 /// This function checks whether the calling app has the Android permissions needed to attest the
119 /// device-unique identifier. It throws an error if the permissions cannot be verified or if the
120 /// caller doesn't have the right permissions. Otherwise it returns silently.
check_unique_id_attestation_permissions() -> anyhow::Result<()>121 pub fn check_unique_id_attestation_permissions() -> anyhow::Result<()> {
122     check_android_permission("android.permission.REQUEST_UNIQUE_ID_ATTESTATION")
123 }
124 
check_android_permission(permission: &str) -> anyhow::Result<()>125 fn check_android_permission(permission: &str) -> anyhow::Result<()> {
126     let permission_controller: Strong<dyn IPermissionController::IPermissionController> =
127         binder::get_interface("permission")?;
128 
129     let binder_result = {
130         let _wp = watchdog::watch_millis(
131             "In check_device_attestation_permissions: calling checkPermission.",
132             500,
133         );
134         permission_controller.checkPermission(
135             permission,
136             ThreadState::get_calling_pid(),
137             ThreadState::get_calling_uid() as i32,
138         )
139     };
140     let has_permissions =
141         map_binder_status(binder_result).context(ks_err!("checkPermission failed"))?;
142     match has_permissions {
143         true => Ok(()),
144         false => Err(Error::Km(ErrorCode::CANNOT_ATTEST_IDS))
145             .context(ks_err!("caller does not have the permission to attest device IDs")),
146     }
147 }
148 
149 /// Converts a set of key characteristics as returned from KeyMint into the internal
150 /// representation of the keystore service.
key_characteristics_to_internal( key_characteristics: Vec<KeyCharacteristics>, ) -> Vec<KeyParameter>151 pub fn key_characteristics_to_internal(
152     key_characteristics: Vec<KeyCharacteristics>,
153 ) -> Vec<KeyParameter> {
154     key_characteristics
155         .into_iter()
156         .flat_map(|aidl_key_char| {
157             let sec_level = aidl_key_char.securityLevel;
158             aidl_key_char
159                 .authorizations
160                 .into_iter()
161                 .map(move |aidl_kp| KeyParameter::new(aidl_kp.into(), sec_level))
162         })
163         .collect()
164 }
165 
166 /// This function can be used to upgrade key blobs on demand. The return value of
167 /// `km_op` is inspected and if ErrorCode::KEY_REQUIRES_UPGRADE is encountered,
168 /// an attempt is made to upgrade the key blob. On success `new_blob_handler` is called
169 /// with the upgraded blob as argument. Then `km_op` is called a second time with the
170 /// upgraded blob as argument. On success a tuple of the `km_op`s result and the
171 /// optional upgraded blob is returned.
upgrade_keyblob_if_required_with<T, KmOp, NewBlobHandler>( km_dev: &dyn IKeyMintDevice, key_blob: &[u8], upgrade_params: &[KmKeyParameter], km_op: KmOp, new_blob_handler: NewBlobHandler, ) -> Result<(T, Option<Vec<u8>>)> where KmOp: Fn(&[u8]) -> Result<T, Error>, NewBlobHandler: FnOnce(&[u8]) -> Result<()>,172 pub fn upgrade_keyblob_if_required_with<T, KmOp, NewBlobHandler>(
173     km_dev: &dyn IKeyMintDevice,
174     key_blob: &[u8],
175     upgrade_params: &[KmKeyParameter],
176     km_op: KmOp,
177     new_blob_handler: NewBlobHandler,
178 ) -> Result<(T, Option<Vec<u8>>)>
179 where
180     KmOp: Fn(&[u8]) -> Result<T, Error>,
181     NewBlobHandler: FnOnce(&[u8]) -> Result<()>,
182 {
183     match km_op(key_blob) {
184         Err(Error::Km(ErrorCode::KEY_REQUIRES_UPGRADE)) => {
185             let upgraded_blob = {
186                 let _wp = watchdog::watch_millis(
187                     "In utils::upgrade_keyblob_if_required_with: calling upgradeKey.",
188                     500,
189                 );
190                 map_km_error(km_dev.upgradeKey(key_blob, upgrade_params))
191             }
192             .context(ks_err!("Upgrade failed."))?;
193 
194             new_blob_handler(&upgraded_blob).context(ks_err!("calling new_blob_handler."))?;
195 
196             km_op(&upgraded_blob)
197                 .map(|v| (v, Some(upgraded_blob)))
198                 .context(ks_err!("Calling km_op after upgrade."))
199         }
200         r => r.map(|v| (v, None)).context(ks_err!("Calling km_op.")),
201     }
202 }
203 
204 /// Converts a set of key characteristics from the internal representation into a set of
205 /// Authorizations as they are used to convey key characteristics to the clients of keystore.
key_parameters_to_authorizations( parameters: Vec<crate::key_parameter::KeyParameter>, ) -> Vec<Authorization>206 pub fn key_parameters_to_authorizations(
207     parameters: Vec<crate::key_parameter::KeyParameter>,
208 ) -> Vec<Authorization> {
209     parameters.into_iter().map(|p| p.into_authorization()).collect()
210 }
211 
212 #[allow(clippy::unnecessary_cast)]
213 /// This returns the current time (in milliseconds) as an instance of a monotonic clock,
214 /// by invoking the system call since Rust does not support getting monotonic time instance
215 /// as an integer.
get_current_time_in_milliseconds() -> i64216 pub fn get_current_time_in_milliseconds() -> i64 {
217     let mut current_time = libc::timespec { tv_sec: 0, tv_nsec: 0 };
218     // Following unsafe block includes one system call to get monotonic time.
219     // Therefore, it is not considered harmful.
220     unsafe { libc::clock_gettime(libc::CLOCK_MONOTONIC_RAW, &mut current_time) };
221     current_time.tv_sec as i64 * 1000 + (current_time.tv_nsec as i64 / 1_000_000)
222 }
223 
224 /// Converts a response code as returned by the Android Protected Confirmation HIDL compatibility
225 /// module (keystore2_apc_compat) into a ResponseCode as defined by the APC AIDL
226 /// (android.security.apc) spec.
compat_2_response_code(rc: u32) -> ApcResponseCode227 pub fn compat_2_response_code(rc: u32) -> ApcResponseCode {
228     match rc {
229         APC_COMPAT_ERROR_OK => ApcResponseCode::OK,
230         APC_COMPAT_ERROR_CANCELLED => ApcResponseCode::CANCELLED,
231         APC_COMPAT_ERROR_ABORTED => ApcResponseCode::ABORTED,
232         APC_COMPAT_ERROR_OPERATION_PENDING => ApcResponseCode::OPERATION_PENDING,
233         APC_COMPAT_ERROR_IGNORED => ApcResponseCode::IGNORED,
234         APC_COMPAT_ERROR_SYSTEM_ERROR => ApcResponseCode::SYSTEM_ERROR,
235         _ => ApcResponseCode::SYSTEM_ERROR,
236     }
237 }
238 
239 /// Converts the UI Options flags as defined by the APC AIDL (android.security.apc) spec into
240 /// UI Options flags as defined by the Android Protected Confirmation HIDL compatibility
241 /// module (keystore2_apc_compat).
ui_opts_2_compat(opt: i32) -> ApcCompatUiOptions242 pub fn ui_opts_2_compat(opt: i32) -> ApcCompatUiOptions {
243     ApcCompatUiOptions {
244         inverted: (opt & FLAG_UI_OPTION_INVERTED) != 0,
245         magnified: (opt & FLAG_UI_OPTION_MAGNIFIED) != 0,
246     }
247 }
248 
249 /// AID offset for uid space partitioning.
250 pub const AID_USER_OFFSET: u32 = rustutils::users::AID_USER_OFFSET;
251 
252 /// AID of the keystore process itself, used for keys that
253 /// keystore generates for its own use.
254 pub const AID_KEYSTORE: u32 = rustutils::users::AID_KEYSTORE;
255 
256 /// Extracts the android user from the given uid.
uid_to_android_user(uid: u32) -> u32257 pub fn uid_to_android_user(uid: u32) -> u32 {
258     rustutils::users::multiuser_get_user_id(uid)
259 }
260 
261 /// Merges and filters two lists of key descriptors. The first input list, legacy_descriptors,
262 /// is assumed to not be sorted or filtered. As such, all key descriptors in that list whose
263 /// alias is less than, or equal to, start_past_alias (if provided) will be removed.
264 /// This list will then be merged with the second list, db_descriptors. The db_descriptors list
265 /// is assumed to be sorted and filtered so the output list will be sorted prior to returning.
266 /// The returned value is a list of KeyDescriptor objects whose alias is greater than
267 /// start_past_alias, sorted and de-duplicated.
merge_and_filter_key_entry_lists( legacy_descriptors: &[KeyDescriptor], db_descriptors: &[KeyDescriptor], start_past_alias: Option<&str>, ) -> Vec<KeyDescriptor>268 fn merge_and_filter_key_entry_lists(
269     legacy_descriptors: &[KeyDescriptor],
270     db_descriptors: &[KeyDescriptor],
271     start_past_alias: Option<&str>,
272 ) -> Vec<KeyDescriptor> {
273     let mut result: Vec<KeyDescriptor> =
274         match start_past_alias {
275             Some(past_alias) => legacy_descriptors
276                 .iter()
277                 .filter(|kd| {
278                     if let Some(alias) = &kd.alias {
279                         alias.as_str() > past_alias
280                     } else {
281                         false
282                     }
283                 })
284                 .cloned()
285                 .collect(),
286             None => legacy_descriptors.to_vec(),
287         };
288 
289     result.extend_from_slice(db_descriptors);
290     result.sort_unstable();
291     result.dedup();
292     result
293 }
294 
estimate_safe_amount_to_return( key_descriptors: &[KeyDescriptor], response_size_limit: usize, ) -> usize295 fn estimate_safe_amount_to_return(
296     key_descriptors: &[KeyDescriptor],
297     response_size_limit: usize,
298 ) -> usize {
299     let mut items_to_return = 0;
300     let mut returned_bytes: usize = 0;
301     // Estimate the transaction size to avoid returning more items than what
302     // could fit in a binder transaction.
303     for kd in key_descriptors.iter() {
304         // 4 bytes for the Domain enum
305         // 8 bytes for the Namespace long.
306         returned_bytes += 4 + 8;
307         // Size of the alias string. Includes 4 bytes for length encoding.
308         if let Some(alias) = &kd.alias {
309             returned_bytes += 4 + alias.len();
310         }
311         // Size of the blob. Includes 4 bytes for length encoding.
312         if let Some(blob) = &kd.blob {
313             returned_bytes += 4 + blob.len();
314         }
315         // The binder transaction size limit is 1M. Empirical measurements show
316         // that the binder overhead is 60% (to be confirmed). So break after
317         // 350KB and return a partial list.
318         if returned_bytes > response_size_limit {
319             log::warn!(
320                 "Key descriptors list ({} items) may exceed binder \
321                        size, returning {} items est {} bytes.",
322                 key_descriptors.len(),
323                 items_to_return,
324                 returned_bytes
325             );
326             break;
327         }
328         items_to_return += 1;
329     }
330     items_to_return
331 }
332 
333 /// List all key aliases for a given domain + namespace. whose alias is greater
334 /// than start_past_alias (if provided).
list_key_entries( db: &mut KeystoreDB, domain: Domain, namespace: i64, start_past_alias: Option<&str>, ) -> Result<Vec<KeyDescriptor>>335 pub fn list_key_entries(
336     db: &mut KeystoreDB,
337     domain: Domain,
338     namespace: i64,
339     start_past_alias: Option<&str>,
340 ) -> Result<Vec<KeyDescriptor>> {
341     let legacy_key_descriptors: Vec<KeyDescriptor> = LEGACY_IMPORTER
342         .list_uid(domain, namespace)
343         .context(ks_err!("Trying to list legacy keys."))?;
344 
345     // The results from the database will be sorted and unique
346     let db_key_descriptors: Vec<KeyDescriptor> = db
347         .list_past_alias(domain, namespace, KeyType::Client, start_past_alias)
348         .context(ks_err!("Trying to list keystore database past alias."))?;
349 
350     let merged_key_entries = merge_and_filter_key_entry_lists(
351         &legacy_key_descriptors,
352         &db_key_descriptors,
353         start_past_alias,
354     );
355 
356     const RESPONSE_SIZE_LIMIT: usize = 358400;
357     let safe_amount_to_return =
358         estimate_safe_amount_to_return(&merged_key_entries, RESPONSE_SIZE_LIMIT);
359     Ok(merged_key_entries[..safe_amount_to_return].to_vec())
360 }
361 
362 /// Count all key aliases for a given domain + namespace.
count_key_entries(db: &mut KeystoreDB, domain: Domain, namespace: i64) -> Result<i32>363 pub fn count_key_entries(db: &mut KeystoreDB, domain: Domain, namespace: i64) -> Result<i32> {
364     let legacy_keys = LEGACY_IMPORTER
365         .list_uid(domain, namespace)
366         .context(ks_err!("Trying to list legacy keys."))?;
367 
368     let num_keys_in_db = db.count_keys(domain, namespace, KeyType::Client)?;
369 
370     Ok((legacy_keys.len() + num_keys_in_db) as i32)
371 }
372 
373 /// This module provides helpers for simplified use of the watchdog module.
374 #[cfg(feature = "watchdog")]
375 pub mod watchdog {
376     pub use crate::watchdog::WatchPoint;
377     use crate::watchdog::Watchdog;
378     use lazy_static::lazy_static;
379     use std::sync::Arc;
380     use std::time::Duration;
381 
382     lazy_static! {
383         /// A Watchdog thread, that can be used to create watch points.
384         static ref WD: Arc<Watchdog> = Watchdog::new(Duration::from_secs(10));
385     }
386 
387     /// Sets a watch point with `id` and a timeout of `millis` milliseconds.
watch_millis(id: &'static str, millis: u64) -> Option<WatchPoint>388     pub fn watch_millis(id: &'static str, millis: u64) -> Option<WatchPoint> {
389         Watchdog::watch(&WD, id, Duration::from_millis(millis))
390     }
391 
392     /// Like `watch_millis` but with a callback that is called every time a report
393     /// is printed about this watch point.
watch_millis_with( id: &'static str, millis: u64, callback: impl Fn() -> String + Send + 'static, ) -> Option<WatchPoint>394     pub fn watch_millis_with(
395         id: &'static str,
396         millis: u64,
397         callback: impl Fn() -> String + Send + 'static,
398     ) -> Option<WatchPoint> {
399         Watchdog::watch_with(&WD, id, Duration::from_millis(millis), callback)
400     }
401 }
402 
403 /// Trait implemented by objects that can be used to decrypt cipher text using AES-GCM.
404 pub trait AesGcm {
405     /// Deciphers `data` using the initialization vector `iv` and AEAD tag `tag`
406     /// and AES-GCM. The implementation provides the key material and selects
407     /// the implementation variant, e.g., AES128 or AES265.
decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec>408     fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec>;
409 
410     /// Encrypts `data` and returns the ciphertext, the initialization vector `iv`
411     /// and AEAD tag `tag`. The implementation provides the key material and selects
412     /// the implementation variant, e.g., AES128 or AES265.
encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)>413     fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)>;
414 }
415 
416 /// Marks an object as AES-GCM key.
417 pub trait AesGcmKey {
418     /// Provides access to the raw key material.
key(&self) -> &[u8]419     fn key(&self) -> &[u8];
420 }
421 
422 impl<T: AesGcmKey> AesGcm for T {
decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec>423     fn decrypt(&self, data: &[u8], iv: &[u8], tag: &[u8]) -> Result<ZVec> {
424         aes_gcm_decrypt(data, iv, tag, self.key()).context(ks_err!("Decryption failed"))
425     }
426 
encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)>427     fn encrypt(&self, plaintext: &[u8]) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
428         aes_gcm_encrypt(plaintext, self.key()).context(ks_err!("Encryption failed."))
429     }
430 }
431 
432 /// This module provides empty/noop implementations of the watch dog utility functions.
433 #[cfg(not(feature = "watchdog"))]
434 pub mod watchdog {
435     /// Noop watch point.
436     pub struct WatchPoint();
437     /// Sets a Noop watch point.
watch_millis(_: &'static str, _: u64) -> Option<WatchPoint>438     fn watch_millis(_: &'static str, _: u64) -> Option<WatchPoint> {
439         None
440     }
441 
watch_millis_with( _: &'static str, _: u64, _: impl Fn() -> String + Send + 'static, ) -> Option<WatchPoint>442     pub fn watch_millis_with(
443         _: &'static str,
444         _: u64,
445         _: impl Fn() -> String + Send + 'static,
446     ) -> Option<WatchPoint> {
447         None
448     }
449 }
450 
451 #[cfg(test)]
452 mod tests {
453     use super::*;
454     use anyhow::Result;
455 
456     #[test]
check_device_attestation_permissions_test() -> Result<()>457     fn check_device_attestation_permissions_test() -> Result<()> {
458         check_device_attestation_permissions().or_else(|error| {
459             match error.root_cause().downcast_ref::<Error>() {
460                 // Expected: the context for this test might not be allowed to attest device IDs.
461                 Some(Error::Km(ErrorCode::CANNOT_ATTEST_IDS)) => Ok(()),
462                 // Other errors are unexpected
463                 _ => Err(error),
464             }
465         })
466     }
467 
create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor>468     fn create_key_descriptors_from_aliases(key_aliases: &[&str]) -> Vec<KeyDescriptor> {
469         key_aliases
470             .iter()
471             .map(|key_alias| KeyDescriptor {
472                 domain: Domain::APP,
473                 nspace: 0,
474                 alias: Some(key_alias.to_string()),
475                 blob: None,
476             })
477             .collect::<Vec<KeyDescriptor>>()
478     }
479 
aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String>480     fn aliases_from_key_descriptors(key_descriptors: &[KeyDescriptor]) -> Vec<String> {
481         key_descriptors
482             .iter()
483             .map(
484                 |kd| {
485                     if let Some(alias) = &kd.alias {
486                         String::from(alias)
487                     } else {
488                         String::from("")
489                     }
490                 },
491             )
492             .collect::<Vec<String>>()
493     }
494 
495     #[test]
test_safe_amount_to_return() -> Result<()>496     fn test_safe_amount_to_return() -> Result<()> {
497         let key_aliases = vec!["key1", "key2", "key3"];
498         let key_descriptors = create_key_descriptors_from_aliases(&key_aliases);
499 
500         assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 20), 1);
501         assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 50), 2);
502         assert_eq!(estimate_safe_amount_to_return(&key_descriptors, 100), 3);
503         Ok(())
504     }
505 
506     #[test]
test_merge_and_sort_lists_without_filtering() -> Result<()>507     fn test_merge_and_sort_lists_without_filtering() -> Result<()> {
508         let legacy_key_aliases = vec!["key_c", "key_a", "key_b"];
509         let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
510         let db_key_aliases = vec!["key_a", "key_d"];
511         let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
512         let result =
513             merge_and_filter_key_entry_lists(&legacy_key_descriptors, &db_key_descriptors, None);
514         assert_eq!(aliases_from_key_descriptors(&result), vec!["key_a", "key_b", "key_c", "key_d"]);
515         Ok(())
516     }
517 
518     #[test]
test_merge_and_sort_lists_with_filtering() -> Result<()>519     fn test_merge_and_sort_lists_with_filtering() -> Result<()> {
520         let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
521         let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
522         let db_key_aliases = vec!["key_c", "key_g"];
523         let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
524         let result = merge_and_filter_key_entry_lists(
525             &legacy_key_descriptors,
526             &db_key_descriptors,
527             Some("key_b"),
528         );
529         assert_eq!(aliases_from_key_descriptors(&result), vec!["key_c", "key_e", "key_f", "key_g"]);
530         Ok(())
531     }
532 
533     #[test]
test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()>534     fn test_merge_and_sort_lists_with_filtering_and_dups() -> Result<()> {
535         let legacy_key_aliases = vec!["key_f", "key_a", "key_e", "key_b"];
536         let legacy_key_descriptors = create_key_descriptors_from_aliases(&legacy_key_aliases);
537         let db_key_aliases = vec!["key_d", "key_e", "key_g"];
538         let db_key_descriptors = create_key_descriptors_from_aliases(&db_key_aliases);
539         let result = merge_and_filter_key_entry_lists(
540             &legacy_key_descriptors,
541             &db_key_descriptors,
542             Some("key_c"),
543         );
544         assert_eq!(aliases_from_key_descriptors(&result), vec!["key_d", "key_e", "key_f", "key_g"]);
545         Ok(())
546     }
547 }
548