blob: a0ce4570e6b8a4189c1ffbcc9401326979fdc245 [file] [log] [blame]
// Copyright 2022, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Key blob manipulation functionality.
use crate::{
contains_tag_value, crypto, km_err, tag, try_to_vec, vec_try, Error, FallibleAllocExt,
};
use alloc::{
format,
string::{String, ToString},
vec::Vec,
};
use kmr_derive::AsCborValue;
use kmr_wire::keymint::{
BootInfo, KeyCharacteristics, KeyParam, KeyPurpose, SecurityLevel, VerifiedBootState,
};
use kmr_wire::{cbor, cbor_type_error, AsCborValue, CborError};
use log::{error, info};
use zeroize::ZeroizeOnDrop;
pub mod legacy;
pub mod sdd_mem;
#[cfg(test)]
mod tests;
/// Nonce value of all zeroes used in AES-GCM key encryption.
const ZERO_NONCE: [u8; 12] = [0u8; 12];
/// Identifier for secure deletion secret storage slot.
#[repr(transparent)]
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, AsCborValue)]
pub struct SecureDeletionSlot(pub u32);
/// Keyblob format version.
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Debug, AsCborValue)]
pub enum Version {
/// Version 1.
V1 = 0,
}
/// Encrypted key material, as translated to/from CBOR.
#[derive(Clone, Debug)]
pub enum EncryptedKeyBlob {
/// Version 1 key blob.
V1(EncryptedKeyBlobV1),
// Future versions go here...
}
impl EncryptedKeyBlob {
/// Construct from serialized data, mapping failure to `ErrorCode::InvalidKeyBlob`.
pub fn new(data: &[u8]) -> Result<Self, Error> {
Self::from_slice(data)
.map_err(|e| km_err!(InvalidKeyBlob, "failed to parse keyblob: {:?}", e))
}
/// Return the secure deletion slot for the key, if present.
pub fn secure_deletion_slot(&self) -> Option<SecureDeletionSlot> {
match self {
EncryptedKeyBlob::V1(blob) => blob.secure_deletion_slot,
}
}
/// Return the additional KEK context for the key.
pub fn kek_context(&self) -> &[u8] {
match self {
EncryptedKeyBlob::V1(blob) => &blob.kek_context,
}
}
}
impl AsCborValue for EncryptedKeyBlob {
fn from_cbor_value(value: cbor::value::Value) -> Result<Self, CborError> {
let mut a = match value {
cbor::value::Value::Array(a) if a.len() == 2 => a,
_ => return cbor_type_error(&value, "arr len 2"),
};
let inner = a.remove(1);
let version = Version::from_cbor_value(a.remove(0))?;
match version {
Version::V1 => Ok(Self::V1(EncryptedKeyBlobV1::from_cbor_value(inner)?)),
}
}
fn to_cbor_value(self) -> Result<cbor::value::Value, CborError> {
Ok(match self {
EncryptedKeyBlob::V1(inner) => cbor::value::Value::Array(
vec_try![Version::V1.to_cbor_value()?, inner.to_cbor_value()?]
.map_err(|_e| CborError::AllocationFailed)?,
),
})
}
fn cddl_typename() -> Option<String> {
Some("EncryptedKeyBlob".to_string())
}
fn cddl_schema() -> Option<String> {
Some(format!(
"&(
[{}, {}] ; Version::V1
)",
Version::V1 as i32,
EncryptedKeyBlobV1::cddl_ref()
))
}
}
/// Encrypted key material, as translated to/from CBOR.
#[derive(Clone, Debug, AsCborValue)]
pub struct EncryptedKeyBlobV1 {
/// Characteristics associated with the key.
pub characteristics: Vec<KeyCharacteristics>,
/// Nonce used for the key derivation.
pub key_derivation_input: [u8; 32],
/// Opaque context data needed for root KEK retrieval.
pub kek_context: Vec<u8>,
/// Key material encrypted with AES-GCM with:
/// - key produced by [`derive_kek`]
/// - plaintext is the CBOR-serialization of [`crypto::KeyMaterial`]
/// - nonce is all zeroes
/// - no additional data.
pub encrypted_key_material: coset::CoseEncrypt0,
/// Identifier for a slot in secure storage that holds additional secret values
/// that are required to derive the key encryption key.
pub secure_deletion_slot: Option<SecureDeletionSlot>,
}
/// Trait to handle keyblobs in a format from a previous implementation.
pub trait LegacyKeyHandler {
/// Indicate whether a keyblob is a legacy key format.
fn is_legacy_key(&self, keyblob: &[u8], params: &[KeyParam], root_of_trust: &BootInfo) -> bool {
// The `convert_legacy_key` method includes a security level parameter so that a new
// keyblob can be emitted with the key characterstics assigned appropriately. However,
// for this method the new keyblob is thrown away, so just use `TrustedEnvironment`.
match self.convert_legacy_key(
keyblob,
params,
root_of_trust,
SecurityLevel::TrustedEnvironment,
) {
Ok(_blob) => {
// Successfully converted the keyblob into current format, so assume that means
// that the keyblob was indeed in the legacy format.
true
}
Err(e) => {
info!("legacy keyblob conversion attempt failed: {:?}", e);
false
}
}
}
/// Convert a potentially-legacy key into current format. Note that any secure deletion data
/// associated with the old keyblob should not be deleted until a subsequent call to
/// `delete_legacy_key` arrives.
fn convert_legacy_key(
&self,
keyblob: &[u8],
params: &[KeyParam],
root_of_trust: &BootInfo,
sec_level: SecurityLevel,
) -> Result<PlaintextKeyBlob, Error>;
/// Delete a potentially-legacy keyblob.
fn delete_legacy_key(&mut self, keyblob: &[u8]) -> Result<(), Error>;
}
/// Secret data that can be mixed into the key derivation inputs for keys; if the secret data is
/// lost, the key is effectively deleted because the key encryption key for the keyblob cannot be
/// re-derived.
#[derive(Clone, PartialEq, Eq, AsCborValue, ZeroizeOnDrop)]
pub struct SecureDeletionData {
/// Secret value that is wiped on factory reset. This should be populated for all keys, to
/// ensure that a factory reset invalidates all keys.
pub factory_reset_secret: [u8; 32],
/// Per-key secret value that is wiped on deletion of a specific key. This is only populated
/// for keys with secure deletion support; for other keys this field will be all zeroes.
pub secure_deletion_secret: [u8; 16],
}
/// Indication of what kind of key operation requires a secure deletion slot.
#[derive(Clone, Copy, PartialEq, Eq)]
pub enum SlotPurpose {
/// Secure deletion slot needed for key generation.
KeyGeneration,
/// Secure deletion slot needed for key import.
KeyImport,
/// Secure deletion slot needed for upgrade of an existing key.
KeyUpgrade,
}
/// Manager for the mapping between secure deletion slots and the corresponding
/// [`SecureDeletionData`] instances.
pub trait SecureDeletionSecretManager {
/// Return a [`SecureDeletionData`] that has the `factory_reset_secret` populated but which has
/// all zeroes for the `secure_deletion_secret`. If a factory reset secret has not yet been
/// created, do so (possibly using `rng`)
fn get_or_create_factory_reset_secret(
&mut self,
rng: &mut dyn crypto::Rng,
) -> Result<SecureDeletionData, Error>;
/// Return a [`SecureDeletionData`] that has the `factory_reset_secret` populated
/// but which has all zeroes for the `secure_deletion_secret`.
fn get_factory_reset_secret(&self) -> Result<SecureDeletionData, Error>;
/// Find an empty slot, populate it with a fresh [`SecureDeletionData`] that includes a per-key
/// secret, and return the slot. If the purpose is `SlotPurpose::KeyUpgrade`, there will be a
/// subsequent call to `delete_secret()` for the slot associated with the original keyblob;
/// implementations should reserve additional expansion space to allow for this.
fn new_secret(
&mut self,
rng: &mut dyn crypto::Rng,
purpose: SlotPurpose,
) -> Result<(SecureDeletionSlot, SecureDeletionData), Error>;
/// Retrieve a [`SecureDeletionData`] identified by `slot`.
fn get_secret(&self, slot: SecureDeletionSlot) -> Result<SecureDeletionData, Error>;
/// Delete the [`SecureDeletionData`] identified by `slot`.
fn delete_secret(&mut self, slot: SecureDeletionSlot) -> Result<(), Error>;
/// Delete all secure deletion data, including the factory reset secret.
fn delete_all(&mut self);
}
/// RAII class to hold a secure deletion slot. The slot is deleted when the holder is dropped.
struct SlotHolder<'a> {
mgr: &'a mut dyn SecureDeletionSecretManager,
slot: Option<SecureDeletionSlot>,
}
impl Drop for SlotHolder<'_> {
fn drop(&mut self) {
if let Some(slot) = self.slot.take() {
if let Err(e) = self.mgr.delete_secret(slot) {
error!("Failed to delete recently-acquired SDD slot {:?}: {:?}", slot, e);
}
}
}
}
impl<'a> SlotHolder<'a> {
/// Reserve a new secure deletion slot.
fn new(
mgr: &'a mut dyn SecureDeletionSecretManager,
rng: &mut dyn crypto::Rng,
purpose: SlotPurpose,
) -> Result<(Self, SecureDeletionData), Error> {
let (slot, sdd) = mgr.new_secret(rng, purpose)?;
Ok((Self { mgr, slot: Some(slot) }, sdd))
}
/// Acquire ownership of the secure deletion slot.
fn consume(mut self) -> SecureDeletionSlot {
self.slot.take().unwrap()
}
}
/// Root of trust information for binding into keyblobs.
#[derive(Debug, Clone, AsCborValue)]
pub struct RootOfTrustInfo {
/// Verified boot key.
pub verified_boot_key: Vec<u8>,
/// Whether the bootloader is locked.
pub device_boot_locked: bool,
/// State of verified boot for the device.
pub verified_boot_state: VerifiedBootState,
}
/// Derive a key encryption key used for key blob encryption. The key is an AES-256 key derived
/// from `root_key` using HKDF (RFC 5869) with HMAC-SHA256:
/// - input keying material = a root key held in hardware. If it contains explicit key material,
/// perform full HKDF. If the root key is an opaque one, we assume that
/// the key is able to be directly used on the HKDF expand step.
/// - salt = absent
/// - info = the following three or four chunks of context data concatenated:
/// - content of `key_derivation_input` (which is random data)
/// - CBOR-serialization of `characteristics`
/// - CBOR-serialized array of additional `KeyParam` items in `hidden`
/// - (if `sdd` provided) CBOR serialization of the `SecureDeletionData`
pub fn 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> {
let mut info = try_to_vec(key_derivation_input)?;
info.try_extend_from_slice(&characteristics.into_vec()?)?;
info.try_extend_from_slice(&hidden.into_vec()?)?;
if let Some(sdd) = sdd {
info.try_extend_from_slice(&sdd.into_vec()?)?;
}
let data = match root_key {
crypto::OpaqueOr::Explicit(key_material) => kdf.hkdf(&[], &key_material.0, &info, 32)?,
key @ crypto::OpaqueOr::Opaque(_) => kdf.expand(key, &info, 32)?,
};
Ok(crypto::aes::Key::Aes256(data.try_into().unwrap(/* safe: len checked */)))
}
/// Plaintext key blob.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PlaintextKeyBlob {
/// Characteristics associated with the key.
pub characteristics: Vec<KeyCharacteristics>,
/// Key Material
pub key_material: crypto::KeyMaterial,
}
impl PlaintextKeyBlob {
/// Return the set of key parameters at the provided security level.
pub fn characteristics_at(&self, sec_level: SecurityLevel) -> Result<&[KeyParam], Error> {
tag::characteristics_at(&self.characteristics, sec_level)
}
/// Check that the key is suitable for the given purpose.
pub fn suitable_for(&self, purpose: KeyPurpose, sec_level: SecurityLevel) -> Result<(), Error> {
if contains_tag_value!(self.characteristics_at(sec_level)?, Purpose, purpose) {
Ok(())
} else {
Err(km_err!(IncompatiblePurpose, "purpose {:?} not supported by keyblob", purpose))
}
}
}
/// Consume a plaintext keyblob and emit an encrypted version. If `sdd_mgr` is provided,
/// a secure deletion slot will be embedded into the keyblob.
#[allow(clippy::too_many_arguments)]
pub fn 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> {
// Determine if secure deletion is required by examining the key characteristics at our
// security level.
let requires_sdd = plaintext_keyblob
.characteristics_at(sec_level)?
.iter()
.any(|param| matches!(param, KeyParam::RollbackResistance | KeyParam::UsageCountLimit(1)));
let (slot_holder, sdd) = match (requires_sdd, sdd_mgr) {
(true, Some(sdd_mgr)) => {
// Reserve a slot and store it in a [`SlotHolder`] so that it will definitely be
// released if there are any errors encountered below.
let (holder, sdd) = SlotHolder::new(sdd_mgr, rng, purpose)?;
(Some(holder), Some(sdd))
}
(true, None) => {
return Err(km_err!(
RollbackResistanceUnavailable,
"no secure secret storage available"
))
}
(false, Some(sdd_mgr)) => {
// Create a secure deletion secret that just has the factory reset secret in it.
(None, Some(sdd_mgr.get_or_create_factory_reset_secret(rng)?))
}
(false, None) => {
// No secure storage available, and none explicitly asked for. However, this keyblob
// will survive factory reset.
(None, None)
}
};
let characteristics = plaintext_keyblob.characteristics;
let mut key_derivation_input = [0u8; 32];
rng.fill_bytes(&mut key_derivation_input[..]);
let kek =
derive_kek(kdf, root_key, &key_derivation_input, characteristics.clone(), hidden, sdd)?;
// Encrypt the plaintext key material into a `Cose_Encrypt0` structure.
let cose_encrypt = coset::CoseEncrypt0Builder::new()
.protected(coset::HeaderBuilder::new().algorithm(coset::iana::Algorithm::A256GCM).build())
.try_create_ciphertext::<_, Error>(
&plaintext_keyblob.key_material.into_vec()?,
&[],
move |pt, aad| {
let mut op = aes.begin_aead(
kek.into(),
crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
crypto::SymmetricOperation::Encrypt,
)?;
op.update_aad(aad)?;
let mut ct = op.update(pt)?;
ct.try_extend_from_slice(&op.finish()?)?;
Ok(ct)
},
)?
.build();
Ok(EncryptedKeyBlob::V1(EncryptedKeyBlobV1 {
characteristics,
key_derivation_input,
kek_context: try_to_vec(kek_context)?,
encrypted_key_material: cose_encrypt,
secure_deletion_slot: slot_holder.map(|h| h.consume()),
}))
}
/// Consume an encrypted keyblob and emit an decrypted version.
pub fn 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> {
let EncryptedKeyBlob::V1(encrypted_keyblob) = encrypted_keyblob;
let sdd = match (encrypted_keyblob.secure_deletion_slot, sdd_mgr) {
(Some(slot), Some(sdd_mgr)) => Some(sdd_mgr.get_secret(slot)?),
(Some(_slot), None) => {
return Err(km_err!(
InvalidKeyBlob,
"keyblob has sdd slot but no secure storage available"
))
}
(None, Some(sdd_mgr)) => {
// Keyblob should be bound to (just) the factory reset secret.
Some(sdd_mgr.get_factory_reset_secret()?)
}
(None, None) => None,
};
let characteristics = encrypted_keyblob.characteristics;
let kek = derive_kek(
kdf,
root_key,
&encrypted_keyblob.key_derivation_input,
characteristics.clone(),
hidden,
sdd,
)?;
let cose_encrypt = encrypted_keyblob.encrypted_key_material;
let extended_aad = coset::enc_structure_data(
coset::EncryptionContext::CoseEncrypt0,
cose_encrypt.protected.clone(),
&[], // no external AAD
);
let mut op = aes.begin_aead(
kek.into(),
crypto::aes::GcmMode::GcmTag16 { nonce: ZERO_NONCE },
crypto::SymmetricOperation::Decrypt,
)?;
op.update_aad(&extended_aad)?;
let mut pt_data = op.update(&cose_encrypt.ciphertext.unwrap_or_default())?;
pt_data.try_extend_from_slice(
&op.finish().map_err(|e| km_err!(InvalidKeyBlob, "failed to decrypt keyblob: {:?}", e))?,
)?;
Ok(PlaintextKeyBlob {
characteristics,
key_material: <crypto::KeyMaterial>::from_slice(&pt_data)?,
})
}