Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 1 | // Copyright 2021, 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 | //! Offer keys based on the "boot level" for superencryption. |
| 16 | |
Janis Danisevskis | 0cabd71 | 2021-05-25 11:07:10 -0700 | [diff] [blame] | 17 | use crate::{ |
| 18 | database::{KeyType, KeystoreDB}, |
| 19 | key_parameter::KeyParameterValue, |
| 20 | raw_device::KeyMintDevice, |
| 21 | }; |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 22 | use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{ |
Janis Danisevskis | acebfa2 | 2021-05-25 10:56:10 -0700 | [diff] [blame] | 23 | Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter as KmKeyParameter, |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 24 | KeyPurpose::KeyPurpose, SecurityLevel::SecurityLevel, |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 25 | }; |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 26 | use anyhow::{Context, Result}; |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 27 | use keystore2_crypto::{hkdf_expand, ZVec, AES_256_KEY_LENGTH}; |
| 28 | use std::{collections::VecDeque, convert::TryFrom}; |
| 29 | |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 30 | /// Strategies used to prevent later boot stages from using the KM key that protects the level 0 |
| 31 | /// key |
| 32 | #[derive(Debug, PartialEq, Eq, Clone, Copy)] |
| 33 | enum DenyLaterStrategy { |
| 34 | /// set MaxUsesPerBoot to 1. This is much less secure, since the attacker can replace the key |
| 35 | /// itself, and therefore create artifacts which appear to come from early boot. |
| 36 | MaxUsesPerBoot, |
| 37 | /// set the EarlyBootOnly property. This property is only supported in KM from 4.1 on, but |
| 38 | /// it ensures that the level 0 key was genuinely created in early boot |
| 39 | EarlyBootOnly, |
| 40 | } |
| 41 | |
| 42 | fn get_level_zero_key_km_and_strategy() -> Result<(KeyMintDevice, DenyLaterStrategy)> { |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 43 | let tee = KeyMintDevice::get(SecurityLevel::TRUSTED_ENVIRONMENT) |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 44 | .context("In get_level_zero_key_km_and_strategy: Get TEE instance failed.")?; |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 45 | if tee.version() >= KeyMintDevice::KEY_MASTER_V4_1 { |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 46 | Ok((tee, DenyLaterStrategy::EarlyBootOnly)) |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 47 | } else { |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 48 | match KeyMintDevice::get_or_none(SecurityLevel::STRONGBOX) |
| 49 | .context("In get_level_zero_key_km_and_strategy: Get Strongbox instance failed.")? |
| 50 | { |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 51 | Some(strongbox) if strongbox.version() >= KeyMintDevice::KEY_MASTER_V4_1 => { |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 52 | Ok((strongbox, DenyLaterStrategy::EarlyBootOnly)) |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 53 | } |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 54 | _ => Ok((tee, DenyLaterStrategy::MaxUsesPerBoot)), |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 55 | } |
| 56 | } |
| 57 | } |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 58 | |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 59 | /// This is not thread safe; caller must hold a lock before calling. |
| 60 | /// In practice the caller is SuperKeyManager and the lock is the |
| 61 | /// Mutex on its internal state. |
| 62 | pub fn get_level_zero_key(db: &mut KeystoreDB) -> Result<ZVec> { |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 63 | let (km_dev, deny_later_strategy) = get_level_zero_key_km_and_strategy() |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 64 | .context("In get_level_zero_key: get preferred KM instance failed")?; |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 65 | log::info!( |
| 66 | "In get_level_zero_key: security_level={:?}, deny_later_strategy={:?}", |
| 67 | km_dev.security_level(), |
| 68 | deny_later_strategy |
| 69 | ); |
| 70 | let required_security_level = km_dev.security_level(); |
| 71 | let required_param: KmKeyParameter = match deny_later_strategy { |
| 72 | DenyLaterStrategy::EarlyBootOnly => KeyParameterValue::EarlyBootOnly, |
| 73 | DenyLaterStrategy::MaxUsesPerBoot => KeyParameterValue::MaxUsesPerBoot(1), |
| 74 | } |
| 75 | .into(); |
| 76 | let params = vec![ |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 77 | KeyParameterValue::Algorithm(Algorithm::HMAC).into(), |
| 78 | KeyParameterValue::Digest(Digest::SHA_2_256).into(), |
| 79 | KeyParameterValue::KeySize(256).into(), |
| 80 | KeyParameterValue::MinMacLength(256).into(), |
| 81 | KeyParameterValue::KeyPurpose(KeyPurpose::SIGN).into(), |
Paul Crowley | eb964cf | 2021-04-19 18:14:15 -0700 | [diff] [blame] | 82 | KeyParameterValue::NoAuthRequired.into(), |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 83 | required_param.clone(), |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 84 | ]; |
Janis Danisevskis | 5c74821 | 2021-05-17 17:13:56 -0700 | [diff] [blame] | 85 | |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 86 | let key_desc = KeyMintDevice::internal_descriptor("boot_level_key".to_string()); |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 87 | let (key_id_guard, key_entry) = km_dev |
Janis Danisevskis | 0cabd71 | 2021-05-25 11:07:10 -0700 | [diff] [blame] | 88 | .lookup_or_generate_key(db, &key_desc, KeyType::Client, ¶ms, |key_characteristics| { |
Janis Danisevskis | acebfa2 | 2021-05-25 10:56:10 -0700 | [diff] [blame] | 89 | key_characteristics.iter().any(|kc| { |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 90 | if kc.securityLevel != required_security_level { |
| 91 | log::error!( |
| 92 | "In get_level_zero_key: security level expected={:?} got={:?}", |
| 93 | required_security_level, |
| 94 | kc.securityLevel |
| 95 | ); |
| 96 | return false; |
Janis Danisevskis | acebfa2 | 2021-05-25 10:56:10 -0700 | [diff] [blame] | 97 | } |
Paul Crowley | e1e1723 | 2022-08-08 16:17:18 -0700 | [diff] [blame^] | 98 | if !kc.authorizations.iter().any(|a| a == &required_param) { |
| 99 | log::error!( |
| 100 | "In get_level_zero_key: required param absent {:?}", |
| 101 | required_param |
| 102 | ); |
| 103 | return false; |
| 104 | } |
| 105 | true |
Janis Danisevskis | acebfa2 | 2021-05-25 10:56:10 -0700 | [diff] [blame] | 106 | }) |
| 107 | }) |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 108 | .context("In get_level_zero_key: lookup_or_generate_key failed")?; |
| 109 | |
Janis Danisevskis | e2d774b | 2021-05-27 10:39:47 -0700 | [diff] [blame] | 110 | let params = [ |
| 111 | KeyParameterValue::MacLength(256).into(), |
| 112 | KeyParameterValue::Digest(Digest::SHA_2_256).into(), |
| 113 | ]; |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 114 | let level_zero_key = km_dev |
| 115 | .use_key_in_one_step( |
| 116 | db, |
Paul Crowley | 618869e | 2021-04-08 20:30:54 -0700 | [diff] [blame] | 117 | &key_id_guard, |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 118 | &key_entry, |
| 119 | KeyPurpose::SIGN, |
| 120 | ¶ms, |
Paul Crowley | 618869e | 2021-04-08 20:30:54 -0700 | [diff] [blame] | 121 | None, |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 122 | b"Create boot level key", |
| 123 | ) |
| 124 | .context("In get_level_zero_key: use_key_in_one_step failed")?; |
| 125 | // TODO: this is rather unsatisfactory, we need a better way to handle |
| 126 | // sensitive binder returns. |
| 127 | let level_zero_key = ZVec::try_from(level_zero_key) |
| 128 | .context("In get_level_zero_key: conversion to ZVec failed")?; |
| 129 | Ok(level_zero_key) |
| 130 | } |
| 131 | |
| 132 | /// Holds the key for the current boot level, and a cache of future keys generated as required. |
| 133 | /// When the boot level advances, keys prior to the current boot level are securely dropped. |
| 134 | pub struct BootLevelKeyCache { |
| 135 | /// Least boot level currently accessible, if any is. |
| 136 | current: usize, |
| 137 | /// Invariant: cache entry *i*, if it exists, holds the HKDF key for boot level |
| 138 | /// *i* + `current`. If the cache is non-empty it can be grown forwards, but it cannot be |
| 139 | /// grown backwards, so keys below `current` are inaccessible. |
| 140 | /// `cache.clear()` makes all keys inaccessible. |
| 141 | cache: VecDeque<ZVec>, |
| 142 | } |
| 143 | |
| 144 | impl BootLevelKeyCache { |
| 145 | const HKDF_ADVANCE: &'static [u8] = b"Advance KDF one step"; |
| 146 | const HKDF_AES: &'static [u8] = b"Generate AES-256-GCM key"; |
| 147 | const HKDF_KEY_SIZE: usize = 32; |
| 148 | |
| 149 | /// Initialize the cache with the level zero key. |
| 150 | pub fn new(level_zero_key: ZVec) -> Self { |
| 151 | let mut cache: VecDeque<ZVec> = VecDeque::new(); |
| 152 | cache.push_back(level_zero_key); |
| 153 | Self { current: 0, cache } |
| 154 | } |
| 155 | |
| 156 | /// Report whether the key for the given level can be inferred. |
| 157 | pub fn level_accessible(&self, boot_level: usize) -> bool { |
| 158 | // If the requested boot level is lower than the current boot level |
| 159 | // or if we have reached the end (`cache.empty()`) we can't retrieve |
| 160 | // the boot key. |
| 161 | boot_level >= self.current && !self.cache.is_empty() |
| 162 | } |
| 163 | |
| 164 | /// Get the HKDF key for boot level `boot_level`. The key for level *i*+1 |
| 165 | /// is calculated from the level *i* key using `hkdf_expand`. |
| 166 | fn get_hkdf_key(&mut self, boot_level: usize) -> Result<Option<&ZVec>> { |
| 167 | if !self.level_accessible(boot_level) { |
| 168 | return Ok(None); |
| 169 | } |
| 170 | // `self.cache.len()` represents the first entry not in the cache, |
| 171 | // so `self.current + self.cache.len()` is the first boot level not in the cache. |
| 172 | let first_not_cached = self.current + self.cache.len(); |
| 173 | |
| 174 | // Grow the cache forwards until it contains the desired boot level. |
| 175 | for _level in first_not_cached..=boot_level { |
| 176 | // We check at the start that cache is non-empty and future iterations only push, |
| 177 | // so this must unwrap. |
| 178 | let highest_key = self.cache.back().unwrap(); |
| 179 | let next_key = hkdf_expand(Self::HKDF_KEY_SIZE, highest_key, Self::HKDF_ADVANCE) |
| 180 | .context("In BootLevelKeyCache::get_hkdf_key: Advancing key one step")?; |
| 181 | self.cache.push_back(next_key); |
| 182 | } |
| 183 | |
| 184 | // If we reach this point, we should have a key at index boot_level - current. |
| 185 | Ok(Some(self.cache.get(boot_level - self.current).unwrap())) |
| 186 | } |
| 187 | |
| 188 | /// Drop keys prior to the given boot level, while retaining the ability to generate keys for |
| 189 | /// that level and later. |
| 190 | pub fn advance_boot_level(&mut self, new_boot_level: usize) -> Result<()> { |
| 191 | if !self.level_accessible(new_boot_level) { |
| 192 | log::error!( |
| 193 | concat!( |
| 194 | "In BootLevelKeyCache::advance_boot_level: ", |
| 195 | "Failed to advance boot level to {}, current is {}, cache size {}" |
| 196 | ), |
| 197 | new_boot_level, |
| 198 | self.current, |
| 199 | self.cache.len() |
| 200 | ); |
| 201 | return Ok(()); |
| 202 | } |
| 203 | |
| 204 | // We `get` the new boot level for the side effect of advancing the cache to a point |
| 205 | // where the new boot level is present. |
| 206 | self.get_hkdf_key(new_boot_level) |
| 207 | .context("In BootLevelKeyCache::advance_boot_level: Advancing cache")?; |
| 208 | |
| 209 | // Then we split the queue at the index of the new boot level and discard the front, |
| 210 | // keeping only the keys with the current boot level or higher. |
| 211 | self.cache = self.cache.split_off(new_boot_level - self.current); |
| 212 | |
| 213 | // The new cache has the new boot level at index 0, so we set `current` to |
| 214 | // `new_boot_level`. |
| 215 | self.current = new_boot_level; |
| 216 | |
| 217 | Ok(()) |
| 218 | } |
| 219 | |
| 220 | /// Drop all keys, effectively raising the current boot level to infinity; no keys can |
| 221 | /// be inferred from this point on. |
| 222 | pub fn finish(&mut self) { |
| 223 | self.cache.clear(); |
| 224 | } |
| 225 | |
| 226 | fn expand_key( |
| 227 | &mut self, |
| 228 | boot_level: usize, |
| 229 | out_len: usize, |
| 230 | info: &[u8], |
| 231 | ) -> Result<Option<ZVec>> { |
| 232 | self.get_hkdf_key(boot_level) |
| 233 | .context("In BootLevelKeyCache::expand_key: Looking up HKDF key")? |
| 234 | .map(|k| hkdf_expand(out_len, k, info)) |
| 235 | .transpose() |
| 236 | .context("In BootLevelKeyCache::expand_key: Calling hkdf_expand") |
| 237 | } |
| 238 | |
| 239 | /// Return the AES-256-GCM key for the current boot level. |
| 240 | pub fn aes_key(&mut self, boot_level: usize) -> Result<Option<ZVec>> { |
| 241 | self.expand_key(boot_level, AES_256_KEY_LENGTH, BootLevelKeyCache::HKDF_AES) |
| 242 | .context("In BootLevelKeyCache::aes_key: expand_key failed") |
| 243 | } |
| 244 | } |
| 245 | |
| 246 | #[cfg(test)] |
| 247 | mod test { |
| 248 | use super::*; |
| 249 | |
| 250 | #[test] |
| 251 | fn test_output_is_consistent() -> Result<()> { |
| 252 | let initial_key = b"initial key"; |
| 253 | let mut blkc = BootLevelKeyCache::new(ZVec::try_from(initial_key as &[u8])?); |
Chris Wailes | 3877f29 | 2021-07-26 19:24:18 -0700 | [diff] [blame] | 254 | assert!(blkc.level_accessible(0)); |
| 255 | assert!(blkc.level_accessible(9)); |
| 256 | assert!(blkc.level_accessible(10)); |
| 257 | assert!(blkc.level_accessible(100)); |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 258 | let v0 = blkc.aes_key(0).unwrap().unwrap(); |
| 259 | let v10 = blkc.aes_key(10).unwrap().unwrap(); |
| 260 | assert_eq!(Some(&v0), blkc.aes_key(0)?.as_ref()); |
| 261 | assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref()); |
| 262 | blkc.advance_boot_level(5)?; |
Chris Wailes | 3877f29 | 2021-07-26 19:24:18 -0700 | [diff] [blame] | 263 | assert!(!blkc.level_accessible(0)); |
| 264 | assert!(blkc.level_accessible(9)); |
| 265 | assert!(blkc.level_accessible(10)); |
| 266 | assert!(blkc.level_accessible(100)); |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 267 | assert_eq!(None, blkc.aes_key(0)?); |
| 268 | assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref()); |
| 269 | blkc.advance_boot_level(10)?; |
Chris Wailes | 3877f29 | 2021-07-26 19:24:18 -0700 | [diff] [blame] | 270 | assert!(!blkc.level_accessible(0)); |
| 271 | assert!(!blkc.level_accessible(9)); |
| 272 | assert!(blkc.level_accessible(10)); |
| 273 | assert!(blkc.level_accessible(100)); |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 274 | assert_eq!(None, blkc.aes_key(0)?); |
| 275 | assert_eq!(Some(&v10), blkc.aes_key(10)?.as_ref()); |
| 276 | blkc.advance_boot_level(0)?; |
Chris Wailes | 3877f29 | 2021-07-26 19:24:18 -0700 | [diff] [blame] | 277 | assert!(!blkc.level_accessible(0)); |
| 278 | assert!(!blkc.level_accessible(9)); |
| 279 | assert!(blkc.level_accessible(10)); |
| 280 | assert!(blkc.level_accessible(100)); |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 281 | assert_eq!(None, blkc.aes_key(0)?); |
| 282 | assert_eq!(Some(v10), blkc.aes_key(10)?); |
| 283 | blkc.finish(); |
Chris Wailes | 3877f29 | 2021-07-26 19:24:18 -0700 | [diff] [blame] | 284 | assert!(!blkc.level_accessible(0)); |
| 285 | assert!(!blkc.level_accessible(9)); |
| 286 | assert!(!blkc.level_accessible(10)); |
| 287 | assert!(!blkc.level_accessible(100)); |
Paul Crowley | 44c02da | 2021-04-08 17:04:43 +0000 | [diff] [blame] | 288 | assert_eq!(None, blkc.aes_key(0)?); |
| 289 | assert_eq!(None, blkc.aes_key(10)?); |
| 290 | Ok(()) |
| 291 | } |
| 292 | } |