Skip to content
Merged
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
2 changes: 0 additions & 2 deletions .github/workflows/build-deb.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ on:
tags:
- "v*.*.*"
pull_request:
branches:
- 'master'

jobs:
build:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/build-rpm.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ on:
tags:
- "v*.*.*"
pull_request:
branches:
- 'master'

jobs:
create-tarball:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/clippy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ on:
tags:
- "v*.*.*"
pull_request:
branches:
- 'master'

jobs:
clippy:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/rust-fmt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ on:
tags:
- "v*.*.*"
pull_request:
branches:
- 'master'

jobs:
fmt:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/shellcheck.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ on:
tags:
- "v*.*.*"
pull_request:
branches:
- 'master'

jobs:
shellcheck:
Expand Down
2 changes: 0 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,6 @@ on:
tags:
- "v*.*.*"
pull_request:
branches:
- 'master'

jobs:
test:
Expand Down
34 changes: 30 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ resolver = "2"
[workspace.package]
authors = ["Kun Lai <laikun@linux.alibaba.com>"]
edition = "2021"
version = "0.3.4"
version = "0.3.5"

[workspace.dependencies]
again = "0.1.2"
Expand Down
107 changes: 66 additions & 41 deletions cryptpilot-core/src/fs/luks2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,21 @@ const LUKS2_VOLUME_KEY_SIZE_BIT_WITH_INTEGRITY: usize = 768;
const LUKS2_VOLUME_KEY_SIZE_BIT_WITHOUT_INTEGRITY: usize = 512;
const LUKS2_SECTOR_SIZE: u32 = 4096;
const LUKS2_SUBSYSTEM_NAME: &str = "cryptpilot";
const LUKS2_SUBSYSTEM_INITIALIZING: &str = "cryptpilot-initializing";

/// Represents the initialization state of a LUKS2 volume managed by cryptpilot.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum VolumeInitState {
/// No LUKS2 header, or LUKS2 header exists but has no cryptpilot subsystem marker.
/// Safe to format.
None,
/// LUKS2 header exists with subsystem="cryptpilot-initializing".
/// A previous init was interrupted mid-way. Safe to re-init.
Initializing,
/// LUKS2 header exists with subsystem="cryptpilot".
/// Volume is fully initialized and ready to open.
Ready,
}

async fn get_luks2_subsystem(dev: &Path) -> Result<Option<String>> {
/// LUKS2 header structure according to the specification
Expand Down Expand Up @@ -93,38 +108,37 @@ pub async fn format(dev: &Path, passphrase: &Passphrase, integrity: IntegrityTyp
libcryptsetup_rs::set_debug_level(CryptDebugLevel::None);
}

let params = CryptParamsLuks2 {
integrity: Some("hmac(sha256)".to_owned()),
let mut params = CryptParamsLuks2 {
integrity: None,
pbkdf: None,
integrity_params: None,
data_alignment: 0,
data_device: None,
sector_size: LUKS2_SECTOR_SIZE,
label: None,
subsystem: None,
subsystem: Some(LUKS2_SUBSYSTEM_INITIALIZING.to_owned()),
};
let mut params_ref = (&params).try_into()?;

let volume_key = match integrity {
IntegrityType::None => {
libcryptsetup_rs::Either::Right(LUKS2_VOLUME_KEY_SIZE_BIT_WITHOUT_INTEGRITY / 8)
}
IntegrityType::Journal | IntegrityType::NoJournal => {
params.integrity = Some("hmac(sha256)".to_owned());
libcryptsetup_rs::Either::Right(LUKS2_VOLUME_KEY_SIZE_BIT_WITH_INTEGRITY / 8)
}
};

let mut params_ref = (&params).try_into()?;

let mut device = CryptInit::init(&device_path)?;

device.context_handle().format::<CryptParamsLuks2Ref>(
EncryptionFormat::Luks2,
("aes", "xts-plain64"),
None,
volume_key,
match integrity {
IntegrityType::None => None,
IntegrityType::Journal | IntegrityType::NoJournal => Some(&mut params_ref),
},
Some(&mut params_ref),
)?;
device.keyslot_handle().add_by_key(
None,
Expand Down Expand Up @@ -260,42 +274,33 @@ pub async fn is_initialized(dev: &Path) -> Result<bool> {
is_a_cryptpilot_initialized_luks2_volume(dev).await
}

async fn is_a_cryptpilot_initialized_luks2_volume(dev: &Path) -> Result<bool> {
let verbose = get_verbose().await;
let device_path = PathBuf::from(&dev);

// First check if it's a LUKS2 volume using the blocking operation
let is_luks2 = tokio::task::spawn_blocking(move || {
if verbose {
libcryptsetup_rs::set_debug_level(CryptDebugLevel::All);
} else {
libcryptsetup_rs::set_debug_level(CryptDebugLevel::None);
}

let mut device = CryptInit::init(&device_path)?;

let load_success = device.context_handle().load::<()>(None, None).is_ok();

let is_luks2 =
load_success && device.format_handle().get_type()? == EncryptionFormat::Luks2;

Ok::<_, anyhow::Error>(is_luks2)
})
.await?
.with_context(|| format!("Failed to check luks2 initialization status of device {dev:?}"))?;
/// Returns the initialization state of a LUKS2 volume.
///
/// - `None`: no valid LUKS2 header, or header exists but has no cryptpilot marker
/// - `Initializing`: subsystem is "cryptpilot-initializing" (partial init)
/// - `Ready`: subsystem is "cryptpilot" (fully initialized)
pub async fn get_init_state(dev: &Path) -> Result<VolumeInitState> {
// Try to read the subsystem from the raw header.
// If the device is not a valid LUKS2 volume or the header can't be read,
// return None.
let subsystem = match get_luks2_subsystem(dev).await {
Ok(Some(s)) => s,
Ok(None) => return Ok(VolumeInitState::None),
Err(_) => return Ok(VolumeInitState::None),
};

if !is_luks2 {
return Ok(false);
if subsystem == LUKS2_SUBSYSTEM_NAME {
Ok(VolumeInitState::Ready)
} else if subsystem == LUKS2_SUBSYSTEM_INITIALIZING {
Ok(VolumeInitState::Initializing)
} else {
Ok(VolumeInitState::None)
}
}

// Check if the subsystem is set to "cryptpilot"
let subsystem_is_set = get_luks2_subsystem(dev)
.await
.with_context(|| format!("Failed to get LUKS2 device subsystem for {dev:?}"))?
.map(|subsystem| subsystem == LUKS2_SUBSYSTEM_NAME)
.unwrap_or(false);

Ok(subsystem_is_set)
async fn is_a_cryptpilot_initialized_luks2_volume(dev: &Path) -> Result<bool> {
let state = get_init_state(dev).await?;
Ok(state == VolumeInitState::Ready)
}

pub fn is_active(volume: &str) -> bool {
Expand Down Expand Up @@ -372,3 +377,23 @@ impl Drop for TempLuksVolume {
});
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_volume_init_state_variants() {
// Verify enum derives and values are correct
assert_eq!(VolumeInitState::None, VolumeInitState::None);
assert_ne!(VolumeInitState::Ready, VolumeInitState::Initializing);
assert_ne!(VolumeInitState::Ready, VolumeInitState::None);
}

#[test]
fn test_subsystem_constants() {
assert_eq!(LUKS2_SUBSYSTEM_NAME, "cryptpilot");
assert_eq!(LUKS2_SUBSYSTEM_INITIALIZING, "cryptpilot-initializing");
assert_ne!(LUKS2_SUBSYSTEM_NAME, LUKS2_SUBSYSTEM_INITIALIZING);
}
}
1 change: 1 addition & 0 deletions cryptpilot-crypt/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,6 @@ cgroups-rs = "0.3.4"
ctor = "=0.4.1"
rstest = "0.25.0"
rstest_reuse = "0.7.0"
serial_test = "3.0"
tokio-util = {workspace = true}
two-rusty-forks = {version = "0.4.0", features = ["macro"]}
4 changes: 2 additions & 2 deletions cryptpilot-crypt/src/cmd/init.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ async fn persistent_disk_init(
status.description
);
}
VolumeStatusKind::RequiresInit => {
// This is expected, continue with initialization
VolumeStatusKind::RequiresInit | VolumeStatusKind::Initializing => {
// This is expected (or init was interrupted), continue with initialization
}
VolumeStatusKind::ReadyToOpen => {
if !init_options.force_reinit {
Expand Down
21 changes: 15 additions & 6 deletions cryptpilot-crypt/src/cmd/show.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,10 @@ pub enum VolumeStatusKind {
DeviceNotFound,
/// Device exists but initialization check failed (with error details)
CheckFailed,
/// Device requires initialization
/// Device requires initialization (raw disk or no marker)
RequiresInit,
/// Device is in initializing state (partial init, interrupted)
Initializing,
/// Volume is ready to open (either initialized persistent volume or temporary volume)
ReadyToOpen,
/// Volume is currently opened/mapped
Expand Down Expand Up @@ -153,6 +155,7 @@ impl PrintAsTable for [VolumeConfig] {
VolumeStatusKind::Opened => Color::Green,
VolumeStatusKind::ReadyToOpen => Color::Green,
VolumeStatusKind::RequiresInit => Color::Yellow,
VolumeStatusKind::Initializing => Color::Yellow,
VolumeStatusKind::CheckFailed => Color::Red,
VolumeStatusKind::DeviceNotFound => Color::Red,
};
Expand Down Expand Up @@ -261,24 +264,30 @@ impl VolumeConfig {
};
}

// For persistent volumes, check initialization status
match cryptpilot::fs::luks2::is_initialized(&self.dev).await {
Ok(true) => VolumeStatus {
// For persistent volumes, check initialization state
match cryptpilot::fs::luks2::get_init_state(&self.dev).await {
Ok(cryptpilot::fs::luks2::VolumeInitState::Ready) => VolumeStatus {
kind: VolumeStatusKind::ReadyToOpen,
description: format!(
"Device '{:?}' is properly initialized as LUKS2 volume and ready to open",
self.dev
),
},
Ok(false) => VolumeStatus {
Ok(cryptpilot::fs::luks2::VolumeInitState::Initializing) => VolumeStatus {
kind: VolumeStatusKind::Initializing,
description: format!(
"\u{26a0} Device '{:?}' is in initializing state - previous initialization was interrupted",
self.dev
),
},
Ok(cryptpilot::fs::luks2::VolumeInitState::None) => VolumeStatus {
kind: VolumeStatusKind::RequiresInit,
description: format!(
"Device '{:?}' exists but is not a valid LUKS2 volume - needs initialization",
self.dev
),
},
Err(e) => {
// This is the critical case - check failed
let error_msg = format!("{:?}", e);
VolumeStatus {
kind: VolumeStatusKind::CheckFailed,
Expand Down
Loading
Loading