• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 //! Key blob manipulation functionality.
2 
3 use crate::{
4     contains_tag_value, crypto, km_err, tag, try_to_vec, vec_try, Error, FallibleAllocExt,
5 };
6 use alloc::{
7     format,
8     string::{String, ToString},
9     vec::Vec,
10 };
11 use kmr_derive::AsCborValue;
12 use kmr_wire::keymint::{
13     BootInfo, KeyCharacteristics, KeyParam, KeyPurpose, SecurityLevel, VerifiedBootState,
14 };
15 use kmr_wire::{cbor, cbor_type_error, AsCborValue, CborError};
16 use log::{error, info};
17 use zeroize::ZeroizeOnDrop;
18 
19 pub mod legacy;
20 pub mod sdd_mem;
21 
22 #[cfg(test)]
23 mod tests;
24 
25 /// Nonce value of all zeroes used in AES-GCM key encryption.
26 const ZERO_NONCE: [u8; 12] = [0u8; 12];
27 
28 /// Identifier for secure deletion secret storage slot.
29 #[repr(transparent)]
30 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, AsCborValue)]
31 pub struct SecureDeletionSlot(pub u32);
32 
33 /// Keyblob format version.
34 #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, AsCborValue)]
35 pub enum Version {
36     V1 = 0,
37 }
38 
39 /// Encrypted key material, as translated to/from CBOR.
40 #[derive(Clone, Debug)]
41 pub enum EncryptedKeyBlob {
42     V1(EncryptedKeyBlobV1),
43     // Future versions go here...
44 }
45 
46 impl EncryptedKeyBlob {
47     /// Construct from serialized data, mapping failure to `ErrorCode::InvalidKeyBlob`.
new(data: &[u8]) -> Result<Self, Error>48     pub fn new(data: &[u8]) -> Result<Self, Error> {
49         Self::from_slice(data)
50             .map_err(|e| km_err!(InvalidKeyBlob, "failed to parse keyblob: {:?}", e))
51     }
secure_deletion_slot(&self) -> Option<SecureDeletionSlot>52     pub fn secure_deletion_slot(&self) -> Option<SecureDeletionSlot> {
53         match self {
54             EncryptedKeyBlob::V1(blob) => blob.secure_deletion_slot,
55         }
56     }
kek_context(&self) -> &[u8]57     pub fn kek_context(&self) -> &[u8] {
58         match self {
59             EncryptedKeyBlob::V1(blob) => &blob.kek_context,
60         }
61     }
62 }
63 
64 impl AsCborValue for EncryptedKeyBlob {
from_cbor_value(value: cbor::value::Value) -> Result<Self, CborError>65     fn from_cbor_value(value: cbor::value::Value) -> Result<Self, CborError> {
66         let mut a = match value {
67             cbor::value::Value::Array(a) if a.len() == 2 => a,
68             _ => return cbor_type_error(&value, "arr len 2"),
69         };
70         let inner = a.remove(1);
71         let version = Version::from_cbor_value(a.remove(0))?;
72         match version {
73             Version::V1 => Ok(Self::V1(EncryptedKeyBlobV1::from_cbor_value(inner)?)),
74         }
75     }
to_cbor_value(self) -> Result<cbor::value::Value, CborError>76     fn to_cbor_value(self) -> Result<cbor::value::Value, CborError> {
77         Ok(match self {
78             EncryptedKeyBlob::V1(inner) => cbor::value::Value::Array(
79                 vec_try![Version::V1.to_cbor_value()?, inner.to_cbor_value()?]
80                     .map_err(|_e| CborError::AllocationFailed)?,
81             ),
82         })
83     }
cddl_typename() -> Option<String>84     fn cddl_typename() -> Option<String> {
85         Some("EncryptedKeyBlob".to_string())
86     }
cddl_schema() -> Option<String>87     fn cddl_schema() -> Option<String> {
88         Some(format!(
89             "&(
90     [{}, {}] ; Version::V1
91 )",
92             Version::V1 as i32,
93             EncryptedKeyBlobV1::cddl_ref()
94         ))
95     }
96 }
97 
98 /// Encrypted key material, as translated to/from CBOR.
99 #[derive(Clone, Debug, AsCborValue)]
100 pub struct EncryptedKeyBlobV1 {
101     /// Characteristics associated with the key.
102     pub characteristics: Vec<KeyCharacteristics>,
103     /// Nonce used for the key derivation.
104     pub key_derivation_input: [u8; 32],
105     /// Opaque context data needed for root KEK retrieval.
106     pub kek_context: Vec<u8>,
107     /// Key material encrypted with AES-GCM with:
108     ///  - key produced by [`derive_kek`]
109     ///  - plaintext is the CBOR-serialization of [`crypto::KeyMaterial`]
110     ///  - nonce is all zeroes
111     ///  - no additional data.
112     pub encrypted_key_material: coset::CoseEncrypt0,
113     /// Identifier for a slot in secure storage that holds additional secret values
114     /// that are required to derive the key encryption key.
115     pub secure_deletion_slot: Option<SecureDeletionSlot>,
116 }
117 
118 /// Trait to handle keyblobs in a format from a previous implementation.
119 pub trait LegacyKeyHandler {
120     /// Indicate whether a keyblob is a legacy key format.
is_legacy_key(&self, keyblob: &[u8], params: &[KeyParam], root_of_trust: &BootInfo) -> bool121     fn is_legacy_key(&self, keyblob: &[u8], params: &[KeyParam], root_of_trust: &BootInfo) -> bool {
122         // The `convert_legacy_key` method includes a security level parameter so that a new
123         // keyblob can be emitted with the key characterstics assigned appropriately.  However,
124         // for this method the new keyblob is thrown away, so just use `TrustedEnvironment`.
125         match self.convert_legacy_key(
126             keyblob,
127             params,
128             root_of_trust,
129             SecurityLevel::TrustedEnvironment,
130         ) {
131             Ok(_blob) => {
132                 // Successfully converted the keyblob into current format, so assume that means
133                 // that the keyblob was indeed in the legacy format.
134                 true
135             }
136             Err(e) => {
137                 info!("legacy keyblob conversion attempt failed: {:?}", e);
138                 false
139             }
140         }
141     }
142 
143     /// Convert a potentially-legacy key into current format.  Note that any secure deletion data
144     /// associated with the old keyblob should not be deleted until a subsequent call to
145     /// `delete_legacy_key` arrives.
convert_legacy_key( &self, keyblob: &[u8], params: &[KeyParam], root_of_trust: &BootInfo, sec_level: SecurityLevel, ) -> Result<PlaintextKeyBlob, Error>146     fn convert_legacy_key(
147         &self,
148         keyblob: &[u8],
149         params: &[KeyParam],
150         root_of_trust: &BootInfo,
151         sec_level: SecurityLevel,
152     ) -> Result<PlaintextKeyBlob, Error>;
153 
154     /// Delete a potentially-legacy keyblob.
delete_legacy_key(&mut self, keyblob: &[u8]) -> Result<(), Error>155     fn delete_legacy_key(&mut self, keyblob: &[u8]) -> Result<(), Error>;
156 }
157 
158 /// Secret data that can be mixed into the key derivation inputs for keys; if the secret data is
159 /// lost, the key is effectively deleted because the key encryption key for the keyblob cannot be
160 /// re-derived.
161 #[derive(Clone, PartialEq, Eq, AsCborValue, ZeroizeOnDrop)]
162 pub struct SecureDeletionData {
163     /// Secret value that is wiped on factory reset.  This should be populated for all keys, to
164     /// ensure that a factory reset invalidates all keys.
165     pub factory_reset_secret: [u8; 32],
166     /// Per-key secret value that is wiped on deletion of a specific key.  This is only populated
167     /// for keys with secure deletion support; for other keys this field will be all zeroes.
168     pub secure_deletion_secret: [u8; 16],
169 }
170 
171 /// Indication of what kind of key operation requires a secure deletion slot.
172 #[derive(Clone, Copy, PartialEq, Eq)]
173 pub enum SlotPurpose {
174     KeyGeneration,
175     KeyImport,
176     KeyUpgrade,
177 }
178 
179 /// Manager for the mapping between secure deletion slots and the corresponding
180 /// [`SecureDeletionData`] instances.
181 pub trait SecureDeletionSecretManager {
182     /// Return a [`SecureDeletionData`] that has the `factory_reset_secret` populated but which has
183     /// all zeroes for the `secure_deletion_secret`. If a factory reset secret has not yet been
184     /// created, do so (possibly using `rng`)
get_or_create_factory_reset_secret( &mut self, rng: &mut dyn crypto::Rng, ) -> Result<SecureDeletionData, Error>185     fn get_or_create_factory_reset_secret(
186         &mut self,
187         rng: &mut dyn crypto::Rng,
188     ) -> Result<SecureDeletionData, Error>;
189 
190     /// Return a [`SecureDeletionData`] that has the `factory_reset_secret` populated
191     /// but which has all zeroes for the `secure_deletion_secret`.
get_factory_reset_secret(&self) -> Result<SecureDeletionData, Error>192     fn get_factory_reset_secret(&self) -> Result<SecureDeletionData, Error>;
193 
194     /// Find an empty slot, populate it with a fresh [`SecureDeletionData`] that includes a per-key
195     /// secret, and return the slot. If the purpose is `SlotPurpose::KeyUpgrade`, there will be a
196     /// subsequent call to `delete_secret()` for the slot associated with the original keyblob;
197     /// implementations should reserve additional expansion space to allow for this.
new_secret( &mut self, rng: &mut dyn crypto::Rng, purpose: SlotPurpose, ) -> Result<(SecureDeletionSlot, SecureDeletionData), Error>198     fn new_secret(
199         &mut self,
200         rng: &mut dyn crypto::Rng,
201         purpose: SlotPurpose,
202     ) -> Result<(SecureDeletionSlot, SecureDeletionData), Error>;
203 
204     /// Retrieve a [`SecureDeletionData`] identified by `slot`.
get_secret(&self, slot: SecureDeletionSlot) -> Result<SecureDeletionData, Error>205     fn get_secret(&self, slot: SecureDeletionSlot) -> Result<SecureDeletionData, Error>;
206 
207     /// Delete the [`SecureDeletionData`] identified by `slot`.
delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), Error>208     fn delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), Error>;
209 
210     /// Delete all secure deletion data, including the factory reset secret.
delete_all(&mut self)211     fn delete_all(&mut self);
212 }
213 
214 /// RAII class to hold a secure deletion slot.  The slot is deleted when the holder is dropped.
215 struct SlotHolder<'a> {
216     mgr: &'a mut dyn SecureDeletionSecretManager,
217     slot: Option<SecureDeletionSlot>,
218 }
219 
220 impl Drop for SlotHolder<'_> {
drop(&mut self)221     fn drop(&mut self) {
222         if let Some(slot) = self.slot.take() {
223             if let Err(e) = self.mgr.delete_secret(slot) {
224                 error!("Failed to delete recently-acquired SDD slot {:?}: {:?}", slot, e);
225             }
226         }
227     }
228 }
229 
230 impl<'a> SlotHolder<'a> {
231     /// Reserve a new secure deletion slot.
new( mgr: &'a mut dyn SecureDeletionSecretManager, rng: &mut dyn crypto::Rng, purpose: SlotPurpose, ) -> Result<(Self, SecureDeletionData), Error>232     fn new(
233         mgr: &'a mut dyn SecureDeletionSecretManager,
234         rng: &mut dyn crypto::Rng,
235         purpose: SlotPurpose,
236     ) -> Result<(Self, SecureDeletionData), Error> {
237         let (slot, sdd) = mgr.new_secret(rng, purpose)?;
238         Ok((Self { mgr, slot: Some(slot) }, sdd))
239     }
240 
241     /// Acquire ownership of the secure deletion slot.
consume(mut self) -> SecureDeletionSlot242     fn consume(mut self) -> SecureDeletionSlot {
243         self.slot.take().unwrap()
244     }
245 }
246 
247 /// Root of trust information for binding into keyblobs.
248 #[derive(Debug, Clone, AsCborValue)]
249 pub struct RootOfTrustInfo {
250     pub verified_boot_key: Vec<u8>,
251     pub device_boot_locked: bool,
252     pub verified_boot_state: VerifiedBootState,
253 }
254 
255 /// Derive a key encryption key used for key blob encryption. The key is an AES-256 key derived
256 /// from `root_key` using HKDF (RFC 5869) with HMAC-SHA256:
257 /// - input keying material = a root key held in hardware. If it contains explicit key material,
258 ///                           perform full HKDF. If the root key is an opaque one, we assume that
259 ///                           the key is able to be directly used on the HKDF expand step.
260 /// - salt = absent
261 /// - info = the following three or four chunks of context data concatenated:
262 ///    - content of `key_derivation_input` (which is random data)
263 ///    - CBOR-serialization of `characteristics`
264 ///    - CBOR-serialized array of additional `KeyParam` items in `hidden`
265 ///    - (if `sdd` provided) CBOR serialization of the `SecureDeletionData`
derive_kek( kdf: &dyn crypto::Hkdf, root_key: &crypto::OpaqueOr<crypto::hmac::Key>, key_derivation_input: &[u8; 32], characteristics: Vec<KeyCharacteristics>, hidden: Vec<KeyParam>, sdd: Option<SecureDeletionData>, ) -> Result<crypto::aes::Key, Error>266 pub fn derive_kek(
267     kdf: &dyn crypto::Hkdf,
268     root_key: &crypto::OpaqueOr<crypto::hmac::Key>,
269     key_derivation_input: &[u8; 32],
270     characteristics: Vec<KeyCharacteristics>,
271     hidden: Vec<KeyParam>,
272     sdd: Option<SecureDeletionData>,
273 ) -> Result<crypto::aes::Key, Error> {
274     let mut info = try_to_vec(key_derivation_input)?;
275     info.try_extend_from_slice(&characteristics.into_vec()?)?;
276     info.try_extend_from_slice(&hidden.into_vec()?)?;
277     if let Some(sdd) = sdd {
278         info.try_extend_from_slice(&sdd.into_vec()?)?;
279     }
280     let data = match root_key {
281         crypto::OpaqueOr::Explicit(key_material) => kdf.hkdf(&[], &key_material.0, &info, 32)?,
282         key @ crypto::OpaqueOr::Opaque(_) => kdf.expand(key, &info, 32)?,
283     };
284     Ok(crypto::aes::Key::Aes256(data.try_into().unwrap(/* safe: len checked */)))
285 }
286 
287 /// Plaintext key blob.
288 #[derive(Clone, Debug, PartialEq, Eq)]
289 pub struct PlaintextKeyBlob {
290     /// Characteristics associated with the key.
291     pub characteristics: Vec<KeyCharacteristics>,
292     /// Key Material
293     pub key_material: crypto::KeyMaterial,
294 }
295 
296 impl PlaintextKeyBlob {
297     /// Return the set of key parameters at the provided security level.
characteristics_at(&self, sec_level: SecurityLevel) -> Result<&[KeyParam], Error>298     pub fn characteristics_at(&self, sec_level: SecurityLevel) -> Result<&[KeyParam], Error> {
299         tag::characteristics_at(&self.characteristics, sec_level)
300     }
301 
302     /// Check that the key is suitable for the given purpose.
suitable_for(&self, purpose: KeyPurpose, sec_level: SecurityLevel) -> Result<(), Error>303     pub fn suitable_for(&self, purpose: KeyPurpose, sec_level: SecurityLevel) -> Result<(), Error> {
304         if contains_tag_value!(self.characteristics_at(sec_level)?, Purpose, purpose) {
305             Ok(())
306         } else {
307             Err(km_err!(IncompatiblePurpose, "purpose {:?} not supported by keyblob", purpose))
308         }
309     }
310 }
311 
312 /// Consume a plaintext keyblob and emit an encrypted version.  If `sdd_mgr` is provided,
313 /// a secure deletion slot will be embedded into the keyblob.
314 #[allow(clippy::too_many_arguments)]
encrypt( sec_level: SecurityLevel, sdd_mgr: Option<&mut dyn SecureDeletionSecretManager>, aes: &dyn crypto::Aes, kdf: &dyn crypto::Hkdf, rng: &mut dyn crypto::Rng, root_key: &crypto::OpaqueOr<crypto::hmac::Key>, kek_context: &[u8], plaintext_keyblob: PlaintextKeyBlob, hidden: Vec<KeyParam>, purpose: SlotPurpose, ) -> Result<EncryptedKeyBlob, Error>315 pub fn encrypt(
316     sec_level: SecurityLevel,
317     sdd_mgr: Option<&mut dyn SecureDeletionSecretManager>,
318     aes: &dyn crypto::Aes,
319     kdf: &dyn crypto::Hkdf,
320     rng: &mut dyn crypto::Rng,
321     root_key: &crypto::OpaqueOr<crypto::hmac::Key>,
322     kek_context: &[u8],
323     plaintext_keyblob: PlaintextKeyBlob,
324     hidden: Vec<KeyParam>,
325     purpose: SlotPurpose,
326 ) -> Result<EncryptedKeyBlob, Error> {
327     // Determine if secure deletion is required by examining the key characteristics at our
328     // security level.
329     let requires_sdd = plaintext_keyblob
330         .characteristics_at(sec_level)?
331         .iter()
332         .any(|param| matches!(param, KeyParam::RollbackResistance | KeyParam::UsageCountLimit(1)));
333     let (slot_holder, sdd) = match (requires_sdd, sdd_mgr) {
334         (true, Some(sdd_mgr)) => {
335             // Reserve a slot and store it in a [`SlotHolder`] so that it will definitely be
336             // released if there are any errors encountered below.
337             let (holder, sdd) = SlotHolder::new(sdd_mgr, rng, purpose)?;
338             (Some(holder), Some(sdd))
339         }
340         (true, None) => {
341             return Err(km_err!(
342                 RollbackResistanceUnavailable,
343                 "no secure secret storage available"
344             ))
345         }
346         (false, Some(sdd_mgr)) => {
347             // Create a secure deletion secret that just has the factory reset secret in it.
348             (None, Some(sdd_mgr.get_or_create_factory_reset_secret(rng)?))
349         }
350         (false, None) => {
351             // No secure storage available, and none explicitly asked for.  However, this keyblob
352             // will survive factory reset.
353             (None, None)
354         }
355     };
356     let characteristics = plaintext_keyblob.characteristics;
357     let mut key_derivation_input = [0u8; 32];
358     rng.fill_bytes(&mut key_derivation_input[..]);
359     let kek =
360         derive_kek(kdf, root_key, &key_derivation_input, characteristics.clone(), hidden, sdd)?;
361 
362     // Encrypt the plaintext key material into a `Cose_Encrypt0` structure.
363     let cose_encrypt = coset::CoseEncrypt0Builder::new()
364         .protected(coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build())
365         .try_create_ciphertext::<_, Error>(
366             &plaintext_keyblob.key_material.into_vec()?,
367             &[],
368             move |pt, aad| {
369                 let mut op = aes.begin_aead(
370                     kek.into(),
371                     crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
372                     crypto::SymmetricOperation::Encrypt,
373                 )?;
374                 op.update_aad(aad)?;
375                 let mut ct = op.update(pt)?;
376                 ct.try_extend_from_slice(&op.finish()?)?;
377                 Ok(ct)
378             },
379         )?
380         .build();
381 
382     Ok(EncryptedKeyBlob::V1(EncryptedKeyBlobV1 {
383         characteristics,
384         key_derivation_input,
385         kek_context: try_to_vec(kek_context)?,
386         encrypted_key_material: cose_encrypt,
387         secure_deletion_slot: slot_holder.map(|h| h.consume()),
388     }))
389 }
390 
391 /// Consume an encrypted keyblob and emit an decrypted version.
decrypt( sdd_mgr: Option<&dyn SecureDeletionSecretManager>, aes: &dyn crypto::Aes, kdf: &dyn crypto::Hkdf, root_key: &crypto::OpaqueOr<crypto::hmac::Key>, encrypted_keyblob: EncryptedKeyBlob, hidden: Vec<KeyParam>, ) -> Result<PlaintextKeyBlob, Error>392 pub fn decrypt(
393     sdd_mgr: Option<&dyn SecureDeletionSecretManager>,
394     aes: &dyn crypto::Aes,
395     kdf: &dyn crypto::Hkdf,
396     root_key: &crypto::OpaqueOr<crypto::hmac::Key>,
397     encrypted_keyblob: EncryptedKeyBlob,
398     hidden: Vec<KeyParam>,
399 ) -> Result<PlaintextKeyBlob, Error> {
400     let EncryptedKeyBlob::V1(encrypted_keyblob) = encrypted_keyblob;
401     let sdd = match (encrypted_keyblob.secure_deletion_slot, sdd_mgr) {
402         (Some(slot), Some(sdd_mgr)) => Some(sdd_mgr.get_secret(slot)?),
403         (Some(_slot), None) => {
404             return Err(km_err!(
405                 InvalidKeyBlob,
406                 "keyblob has sdd slot but no secure storage available"
407             ))
408         }
409         (None, Some(sdd_mgr)) => {
410             // Keyblob should be bound to (just) the factory reset secret.
411             Some(sdd_mgr.get_factory_reset_secret()?)
412         }
413         (None, None) => None,
414     };
415     let characteristics = encrypted_keyblob.characteristics;
416     let kek = derive_kek(
417         kdf,
418         root_key,
419         &encrypted_keyblob.key_derivation_input,
420         characteristics.clone(),
421         hidden,
422         sdd,
423     )?;
424     let cose_encrypt = encrypted_keyblob.encrypted_key_material;
425 
426     let extended_aad = coset::enc_structure_data(
427         coset::EncryptionContext::CoseEncrypt0,
428         cose_encrypt.protected.clone(),
429         &[], // no external AAD
430     );
431 
432     let mut op = aes.begin_aead(
433         kek.into(),
434         crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
435         crypto::SymmetricOperation::Decrypt,
436     )?;
437     op.update_aad(&extended_aad)?;
438     let mut pt_data = op.update(&cose_encrypt.ciphertext.unwrap_or_default())?;
439     pt_data.try_extend_from_slice(
440         &op.finish().map_err(|e| km_err!(InvalidKeyBlob, "failed to decrypt keyblob: {:?}", e))?,
441     )?;
442 
443     Ok(PlaintextKeyBlob {
444         characteristics,
445         key_material: <crypto::KeyMaterial>::from_slice(&pt_data)?,
446     })
447 }
448