Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 34 additions & 8 deletions openhcl/underhill_attestation/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -701,6 +701,7 @@ async fn get_derived_keys(
};

// Handle various sources of Guest State Protection
let existing_unencrypted = !vmgs.is_encrypted() && !vmgs.was_provisioned_this_boot();
let is_gsp_by_id = key_protector_by_id.found_id && key_protector_by_id.inner.ported != 1;
let is_gsp = key_protector.gsp[ingress_idx].gsp_length != 0;
tracing::info!(
Expand All @@ -714,7 +715,7 @@ async fn get_derived_keys(
let mut requires_gsp_by_id = is_gsp_by_id;

// Attempt GSP
let (gsp_response, no_gsp, requires_gsp) = {
let (gsp_response, gsp_available, no_gsp, requires_gsp) = {
tracing::info!(CVM_ALLOWED, "attempting GSP");

let response = get_gsp_data(get, key_protector).await;
Expand All @@ -728,8 +729,18 @@ async fn get_derived_keys(
"GSP response"
);

let no_gsp = response.extended_status_flags.no_rpc_server()
|| response.encrypted_gsp.length == 0
let no_gsp_available =
response.extended_status_flags.no_rpc_server() || response.encrypted_gsp.length == 0;

let no_gsp = no_gsp_available
// disable if auto and pre-existing guest state is not encrypted or
// encrypted using GspById to prevent encryption changes without
// explicit intent
|| (matches!(
guest_state_encryption_policy,
GuestStateEncryptionPolicy::Auto
) && (is_gsp_by_id || existing_unencrypted))
// disable per encryption policy (first boot only, unless strict)
|| (matches!(
guest_state_encryption_policy,
GuestStateEncryptionPolicy::GspById | GuestStateEncryptionPolicy::None
Expand All @@ -748,20 +759,29 @@ async fn get_derived_keys(
requires_gsp_by_id = true;
}

(response, no_gsp, requires_gsp)
(response, !no_gsp_available, no_gsp, requires_gsp)
};

// Attempt GSP By Id protection if GSP is not available, when changing
// schemes, or as requested
let (gsp_response_by_id, no_gsp_by_id) = if no_gsp || requires_gsp_by_id {
let (gsp_response_by_id, gsp_by_id_available, no_gsp_by_id) = if no_gsp || requires_gsp_by_id {
tracing::info!(CVM_ALLOWED, "attempting GSP By Id");

let gsp_response_by_id = get
.guest_state_protection_data_by_id()
.await
.map_err(GetDerivedKeysError::FetchGuestStateProtectionById)?;

let no_gsp_by_id = gsp_response_by_id.extended_status_flags.no_registry_file()
let no_gsp_by_id_available = gsp_response_by_id.extended_status_flags.no_registry_file();

let no_gsp_by_id = no_gsp_by_id_available
// disable if auto and pre-existing guest state is unencrypted
// to prevent encryption changes without explicit intent
|| (matches!(
guest_state_encryption_policy,
GuestStateEncryptionPolicy::Auto
) && existing_unencrypted)
// disable per encryption policy (first boot only, unless strict)
|| (matches!(
guest_state_encryption_policy,
GuestStateEncryptionPolicy::None
Expand All @@ -771,9 +791,13 @@ async fn get_derived_keys(
Err(GetDerivedKeysError::GspByIdRequiredButNotFound)?
}

(gsp_response_by_id, no_gsp_by_id)
(
gsp_response_by_id,
Some(!no_gsp_by_id_available),
no_gsp_by_id,
)
} else {
(GuestStateProtectionById::new_zeroed(), true)
(GuestStateProtectionById::new_zeroed(), None, true)
};

// If sources of encryption used last are missing, attempt to unseal VMGS key with hardware key
Expand Down Expand Up @@ -858,7 +882,9 @@ async fn get_derived_keys(
tracing::info!(
CVM_ALLOWED,
kek = !no_kek,
gsp_available,
gsp = !no_gsp,
gsp_by_id_available = ?gsp_by_id_available,
gsp_by_id = !no_gsp_by_id,
"Encryption sources"
);
Expand Down
5 changes: 3 additions & 2 deletions vm/devices/get/get_protocol/src/dps_json.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,10 @@ pub enum GuestStateLifetime {
pub enum GuestStateEncryptionPolicy {
/// Use the best encryption available, allowing fallback.
///
/// VMs will be created as or migrated to the best encryption available,
/// VMs will be created using the best encryption available,
/// attempting GspKey, then GspById, and finally leaving the data
/// unencrypted if neither are available.
/// unencrypted if neither are available. VMs will not be migrated
/// to a different encryption method.
#[default]
Auto,
/// Prefer (or require, if strict) no encryption.
Expand Down
13 changes: 12 additions & 1 deletion vm/vmgs/vmgs/src/vmgs_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,7 @@ pub struct Vmgs {
#[cfg_attr(feature = "inspect", inspect(iter_by_index))]
encrypted_metadata_keys: [VmgsEncryptionKey; 2],
reprovisioned: bool,
provisioned_this_boot: bool,

#[cfg_attr(feature = "inspect", inspect(skip))]
logger: Option<Arc<dyn VmgsLogger>>,
Expand Down Expand Up @@ -245,7 +246,9 @@ impl Vmgs {
logger: Option<Arc<dyn VmgsLogger>>,
) -> Result<Self, Error> {
let active_header = Self::format(&mut storage, VMGS_VERSION_3_0).await?;
Self::finish_open(storage, active_header, 0, logger).await
let mut vmgs = Self::finish_open(storage, active_header, 0, logger).await?;
vmgs.provisioned_this_boot = true;
Ok(vmgs)
}

async fn open_inner(
Expand Down Expand Up @@ -367,6 +370,7 @@ impl Vmgs {
metadata_key: VmgsDatastoreKey::new_zeroed(),
encrypted_metadata_keys,
reprovisioned,
provisioned_this_boot: false,

#[cfg(feature = "inspect")]
stats: Default::default(),
Expand Down Expand Up @@ -1522,6 +1526,11 @@ impl Vmgs {
self.encryption_algorithm != EncryptionAlgorithm::NONE
}

/// Whether the VMGS file was provisioned during the most recent boot
pub fn was_provisioned_this_boot(&self) -> bool {
self.provisioned_this_boot
}

fn prepare_new_header(&self, file_table_fcb: &ResolvedFileControlBlock) -> VmgsHeader {
VmgsHeader {
signature: VMGS_SIGNATURE,
Expand Down Expand Up @@ -1992,6 +2001,7 @@ pub mod save_restore {
}
}),
reprovisioned,
provisioned_this_boot: false,
logger,
}
}
Expand Down Expand Up @@ -2020,6 +2030,7 @@ pub mod save_restore {
encrypted_metadata_keys,
logger: _,
reprovisioned,
provisioned_this_boot: _,
} = self;

state::SavedVmgsState {
Expand Down
2 changes: 1 addition & 1 deletion vm/vmgs/vmgs_format/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,5 +220,5 @@ open_enum! {
pub struct VmgsMarkers {
pub reprovisioned: bool,
#[bits(15)]
_reserved: u32,
_reserved: u16,
}
18 changes: 2 additions & 16 deletions vm/vmgs/vmgs_resources/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -72,31 +72,17 @@ pub struct VmgsDisk {
}

/// Guest state encryption policy
///
/// See detailed comments in `get_protocol`
#[derive(MeshPayload, Debug, Clone, Copy)]
pub enum GuestStateEncryptionPolicy {
/// Use the best encryption available, allowing fallback.
///
/// VMs will be created as or migrated to the best encryption available,
/// attempting GspKey, then GspById, and finally leaving the data
/// unencrypted if neither are available.
Auto,
/// Prefer (or require, if strict) no encryption.
///
/// Do not encrypt the guest state unless it is already encrypted and
/// strict encryption policy is disabled.
None(bool),
/// Prefer (or require, if strict) GspById.
///
/// This prevents a VM from being created as or migrated to GspKey even
/// if it is available. Exisiting GspKey encryption will be used unless
/// strict encryption policy is enabled. Fails if the data cannot be
/// encrypted.
GspById(bool),
/// Prefer (or require, if strict) GspKey.
///
/// VMs will be created as or migrated to GspKey. GspById encryption will
/// be used if GspKey is unavailable unless strict encryption policy is
/// enabled. Fails if the data cannot be encrypted.
GspKey(bool),
}

Expand Down