use std::{
convert::TryFrom,
path::{Path, PathBuf},
};
use either::Either;
use serde_json::Value;
use libcryptsetup_rs::{
CryptDevice, CryptInit, CryptVolumeKeyFlags, EncryptionFormat, KeyslotsSize, MetadataSize,
TokenInput,
};
use crate::{
engine::{
strat_engine::{
backstore::crypt::{
consts::{
CLEVIS_LUKS_TOKEN_ID, DEFAULT_CRYPT_KEYSLOTS_SIZE, DEFAULT_CRYPT_METADATA_SIZE,
STRATIS_MEK_SIZE, STRATIS_TOKEN_ID,
},
handle::CryptHandle,
shared::{
acquire_crypt_device, activate, add_keyring_keyslot, clevis_info_from_metadata,
ensure_wiped, interpret_clevis_config, StratisLuks2Token,
},
},
cmd::clevis_luks_bind,
keys::MemoryPrivateFilesystem,
metadata::StratisIdentifiers,
names::format_crypt_name,
},
types::{DevUuid, EncryptionInfo, KeyDescription, PoolUuid},
},
stratis::StratisResult,
};
pub struct CryptInitializer {
physical_path: PathBuf,
identifiers: StratisIdentifiers,
activation_name: String,
}
impl CryptInitializer {
pub fn new(physical_path: PathBuf, pool_uuid: PoolUuid, dev_uuid: DevUuid) -> CryptInitializer {
CryptInitializer {
physical_path,
activation_name: format_crypt_name(&dev_uuid),
identifiers: StratisIdentifiers::new(pool_uuid, dev_uuid),
}
}
fn acquire_crypt_device(&self) -> StratisResult<CryptDevice> {
acquire_crypt_device(&self.physical_path)
}
pub fn initialize(
self,
key_description: Option<&KeyDescription>,
clevis_info: Option<(&str, &Value)>,
) -> StratisResult<CryptHandle> {
let mut clevis_info_owned =
clevis_info.map(|(pin, config)| (pin.to_owned(), config.clone()));
let clevis_parsed = match clevis_info_owned {
Some((ref pin, ref mut config)) => {
let yes = interpret_clevis_config(pin, config)?;
Some((pin.as_str(), &*config, yes))
}
None => None,
};
let mut device = log_on_failure!(
CryptInit::init(&self.physical_path),
"Failed to acquire context for device {} while initializing; \
nothing to clean up",
self.physical_path.display()
);
device.settings_handle().set_metadata_size(
MetadataSize::try_from(DEFAULT_CRYPT_METADATA_SIZE)?,
KeyslotsSize::try_from(DEFAULT_CRYPT_KEYSLOTS_SIZE)?,
)?;
let result = self.initialize_with_err(device, key_description, clevis_parsed);
let mut device = match self.acquire_crypt_device() {
Ok(d) => d,
Err(e) => {
warn!(
"Failed to roll back crypt device initialization; you \
may need to manually wipe this device: {}",
e,
);
return Err(e);
}
};
result
.and_then(|activated_path| {
Ok(CryptHandle::new(
self.physical_path.clone(),
activated_path,
self.identifiers,
EncryptionInfo {
key_description: key_description.cloned(),
clevis_info: clevis_info_from_metadata(&mut device)?,
},
self.activation_name.clone(),
))
})
.map_err(|e| {
if let Err(err) =
Self::rollback(&mut device, &self.physical_path, self.activation_name)
{
warn!(
"Failed to roll back crypt device initialization; you may need to manually wipe this device: {}",
err
);
}
e
})
}
fn initialize_with_keyring(
device: &mut CryptDevice,
key_description: &KeyDescription,
) -> StratisResult<()> {
add_keyring_keyslot(device, key_description, None)?;
Ok(())
}
fn initialize_with_clevis(
&self,
mut device: CryptDevice,
(pin, json, yes): (&str, &Value, bool),
) -> StratisResult<()> {
let fs = log_on_failure!(
MemoryPrivateFilesystem::new(),
"Failed to initialize in memory filesystem for temporary keyfile for
Clevis binding"
);
let keyfile = log_on_failure!(
fs.rand_key(),
"Failed to generate a key with random data for Clevis initialization"
);
let keyslot = log_on_failure!(
device.keyslot_handle().add_by_key(
None,
None,
keyfile.as_ref(),
CryptVolumeKeyFlags::empty(),
),
"Failed to initialize keyslot with provided key in keyring"
);
drop(device);
clevis_luks_bind(
&self.physical_path,
keyfile.keyfile_path(),
CLEVIS_LUKS_TOKEN_ID,
pin,
json,
yes,
)?;
let mut device = self.acquire_crypt_device()?;
device.keyslot_handle().destroy(keyslot)?;
Ok(())
}
fn initialize_with_both(
&self,
mut device: CryptDevice,
key_description: &KeyDescription,
(pin, json, yes): (&str, &Value, bool),
) -> StratisResult<()> {
Self::initialize_with_keyring(&mut device, key_description)?;
let fs = MemoryPrivateFilesystem::new()?;
fs.key_op(key_description, |kf| {
clevis_luks_bind(
&self.physical_path,
kf,
CLEVIS_LUKS_TOKEN_ID,
pin,
json,
yes,
)
})?;
Ok(())
}
fn initialize_with_err(
&self,
mut device: CryptDevice,
key_description: Option<&KeyDescription>,
clevis_info: Option<(&str, &Value, bool)>,
) -> StratisResult<PathBuf> {
log_on_failure!(
device.context_handle().format::<()>(
EncryptionFormat::Luks2,
("aes", "xts-plain64"),
None,
libcryptsetup_rs::Either::Right(STRATIS_MEK_SIZE),
None,
),
"Failed to format device {} with LUKS2 header",
self.physical_path.display()
);
let mut device = match (key_description, clevis_info) {
(Some(kd), Some(ci)) => {
self.initialize_with_both(device, kd, ci)?;
self.acquire_crypt_device()?
}
(Some(kd), _) => {
Self::initialize_with_keyring(&mut device, kd)?;
device
}
(_, Some(ci)) => {
self.initialize_with_clevis(device, ci)?;
self.acquire_crypt_device()?
}
(_, _) => unreachable!(),
};
log_on_failure!(
device.token_handle().json_set(TokenInput::ReplaceToken(
STRATIS_TOKEN_ID,
&StratisLuks2Token {
devname: self.activation_name.clone(),
identifiers: self.identifiers,
}
.into(),
)),
"Failed to create the Stratis token"
);
activate(
if let Some(kd) = key_description {
Either::Left((&mut device, kd))
} else {
Either::Right(&self.physical_path)
},
&self.activation_name,
)
}
pub fn rollback(
device: &mut CryptDevice,
physical_path: &Path,
name: String,
) -> StratisResult<()> {
ensure_wiped(device, physical_path, &name)
}
}