blob: 40257c57b13ff014f6b3db1be208dd7b486357f5 [file] [log] [blame]
// Copyright 2022 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
use super::{format_sources, get_policy, unseal_sources, KeyConsumer};
use crate::device::Device;
use anyhow::{anyhow, Context, Error};
use async_trait::async_trait;
use device_watcher::recursive_wait_and_open;
use fidl::endpoints::Proxy as _;
use fidl_fuchsia_device::ControllerProxy;
use fidl_fuchsia_hardware_block::BlockProxy;
use fidl_fuchsia_hardware_block_encrypted::{DeviceManagerMarker, DeviceManagerProxy};
use fidl_fuchsia_hardware_block_volume::VolumeProxy;
use fs_management::format::DiskFormat;
use {fidl_fuchsia_io as fio, fuchsia_zircon as zx};
/// Fetches a FIDL proxy for accessing zxcrypt management protocol for a given Device.
async fn device_to_device_manager_proxy(device: &dyn Device) -> Result<DeviceManagerProxy, Error> {
let controller = fuchsia_fs::directory::open_in_namespace(
device.topological_path(),
fio::OpenFlags::empty(),
)?;
let zxcrypt = recursive_wait_and_open::<DeviceManagerMarker>(&controller, "zxcrypt")
.await
.context("waiting for zxcrypt device")?;
Ok(DeviceManagerProxy::new(zxcrypt.into_channel().unwrap()))
}
/// Holds the outcome of an unseal attempt via ZxcryptDevice::unseal().
pub enum UnsealOutcome {
Unsealed(ZxcryptDevice),
FormatRequired,
}
/// A BlockDevice representing a zxcrypt wrapped child device.
pub struct ZxcryptDevice {
parent_is_nand: bool,
proxy: DeviceManagerProxy,
inner_device: Box<dyn Device>,
}
impl ZxcryptDevice {
/// Unseals a Zxcrypt BlockDevice and returns it.
/// If device is not in zxcrypt format, return 'FormatRequired'.
pub async fn unseal(outer_device: &mut dyn Device) -> Result<UnsealOutcome, Error> {
if outer_device.content_format().await? != DiskFormat::Zxcrypt {
return Ok(UnsealOutcome::FormatRequired);
}
let proxy = device_to_device_manager_proxy(outer_device).await?;
ZxcryptDevice::from_proxy(outer_device, proxy).await
}
/// Formats a BlockDevice as Zxcrypt and returns it.
pub async fn format(outer_device: &mut dyn Device) -> Result<ZxcryptDevice, Error> {
let proxy = device_to_device_manager_proxy(outer_device).await?;
let policy = get_policy().await?;
let sources = format_sources(policy);
let mut last_err = anyhow!("no keys?");
for source in sources {
let key = source.get_key(KeyConsumer::Zxcrypt).await?;
match zx::ok(proxy.format(&key, 0).await?) {
Ok(()) => {
let zxcrypt_device =
ZxcryptDevice::from_proxy(outer_device, proxy.clone()).await?;
if let UnsealOutcome::Unsealed(zxcrypt_device) = zxcrypt_device {
return Ok(zxcrypt_device);
} else {
return Err(anyhow!("zxcrypt format failed"));
}
}
Err(status) => last_err = status.into(),
}
}
Err(last_err)
}
/// Attempts to unseal a zxcrypt device and return it.
async fn from_proxy(
outer_device: &mut dyn Device,
proxy: DeviceManagerProxy,
) -> Result<UnsealOutcome, Error> {
let policy = get_policy().await?;
let sources = unseal_sources(policy);
let mut last_res = Err(anyhow!("no keys?"));
for source in sources {
let key = source.get_key(KeyConsumer::Zxcrypt).await?;
match zx::ok(proxy.unseal(&key, 0).await?) {
Ok(()) => {
let device = ZxcryptDevice {
parent_is_nand: outer_device.is_nand(),
proxy: proxy.clone(),
inner_device: outer_device.get_child("/zxcrypt/unsealed/block").await?,
};
tracing::info!(
path = device.path(),
topological_path = device.topological_path(),
"created zxcryptdevice"
);
return Ok(UnsealOutcome::Unsealed(device));
}
Err(zx::Status::ACCESS_DENIED) => last_res = Ok(UnsealOutcome::FormatRequired),
Err(status) => last_res = Err(status.into()),
};
}
last_res
}
pub async fn seal(self) -> Result<(), Error> {
zx::ok(self.proxy.seal().await?).map_err(|e| e.into())
}
}
#[async_trait]
impl Device for ZxcryptDevice {
async fn get_block_info(&self) -> Result<fidl_fuchsia_hardware_block::BlockInfo, Error> {
self.inner_device.get_block_info().await
}
fn is_nand(&self) -> bool {
self.parent_is_nand
}
async fn content_format(&mut self) -> Result<DiskFormat, Error> {
self.inner_device.content_format().await
}
fn topological_path(&self) -> &str {
self.inner_device.topological_path()
}
fn path(&self) -> &str {
self.inner_device.path()
}
async fn partition_label(&mut self) -> Result<&str, Error> {
self.inner_device.partition_label().await
}
async fn partition_type(&mut self) -> Result<&[u8; 16], Error> {
self.inner_device.partition_type().await
}
async fn partition_instance(&mut self) -> Result<&[u8; 16], Error> {
self.inner_device.partition_instance().await
}
async fn resize(&mut self, target_size_bytes: u64) -> Result<u64, Error> {
// Nb: The zxcrypt device proxies the BlockVolume protocol and
// changes the extend/shrink offset to account for the
// zxcrypt header (src/devices/block/drivers/zxcrypt/device.cc:193).
let volume_proxy = self.volume_proxy()?;
crate::volume::resize_volume(&volume_proxy, target_size_bytes).await
}
async fn set_partition_max_bytes(&mut self, max_bytes: u64) -> Result<(), Error> {
// Because partition limits are set on the volume manager (not the volume proxy)
// we have to account fo the zxcrypt overheads ourselves.
let extra_bytes = if max_bytes > 0 {
let volume_proxy = self.volume_proxy()?;
let (status, volume_manager_info, _volume_info) = volume_proxy
.get_volume_info()
.await
.context("Transport error on get_volume_info")?;
zx::Status::ok(status).context("get_volume_info failed")?;
let manager = volume_manager_info.ok_or(anyhow!("Expected volume manager info"))?;
manager.slice_size
} else {
0
};
// Add an extra slice for zxcrypt metadata.
self.inner_device.set_partition_max_bytes(max_bytes + extra_bytes).await
}
fn controller(&self) -> &ControllerProxy {
self.inner_device.controller()
}
fn reopen_controller(&self) -> Result<ControllerProxy, Error> {
self.inner_device.reopen_controller()
}
fn block_proxy(&self) -> Result<BlockProxy, Error> {
self.inner_device.block_proxy()
}
fn volume_proxy(&self) -> Result<VolumeProxy, Error> {
self.inner_device.volume_proxy()
}
async fn get_child(&self, suffix: &str) -> Result<Box<dyn Device>, Error> {
self.inner_device.get_child(suffix).await
}
}