@@ -47,6 +47,18 @@ pub enum BlockDeviceEncryptType {
4747 /// Optional name for /dev/mapper/<name>
4848 #[ serde( rename = "mapperName" ) ]
4949 mapper_name : Option < String > ,
50+
51+ /// Optional path to the LUKS header file (detached header).
52+ /// When creating (source empty): use this path for the header file; if
53+ /// not set, a path is generated and removed on umount.
54+ /// When opening (source encrypted): use this path to open the device;
55+ /// if not set, the header is read from the device.
56+ #[ serde(
57+ default ,
58+ skip_serializing_if = "Option::is_none" ,
59+ rename = "headerPath"
60+ ) ]
61+ header_path : Option < String > ,
5062 } ,
5163}
5264
@@ -212,21 +224,29 @@ impl BlockDevice {
212224 BlockDeviceEncryptType :: Luks2 {
213225 data_integrity,
214226 mapper_name,
227+ header_path : user_header_path,
215228 } => {
216229 let integrity = data_integrity. map_or ( false , |e| e. parse ( ) . unwrap_or ( false ) ) ;
217230 let formatter = Luks2Formatter :: default ( ) . with_integrity ( integrity) ;
218231 // 3.1 if the source type is empty, encrypt the device and create detached header
219232 let header_path = if parameters. source_type == SourceType :: Empty {
220233 warn ! ( "encrypting the device. This will wipe original data on the disk." ) ;
221- let header_path = luks2:: luks_header_path ( & device_path) ;
222- luks2:: prepare_luks_header_file ( & header_path) ?;
223- self . temp_paths . push ( header_path. clone ( ) ) ;
234+ let path = if user_header_path. is_none ( ) {
235+ let path = luks2:: luks_header_path ( & device_path) ;
236+ // Remove header on umount when we generated it (not user-provided)
237+ self . temp_paths . push ( path. clone ( ) ) ;
238+ path
239+ } else {
240+ user_header_path. clone ( ) . unwrap ( )
241+ } ;
242+ luks2:: prepare_luks_header_file ( & path) ?;
224243 formatter
225- . encrypt_device ( & device_path, Some ( & header_path ) , key. clone ( ) )
244+ . encrypt_device ( & device_path, Some ( & path ) , key. clone ( ) )
226245 . map_err ( |source| BlockDeviceError :: Luks2Error { source } ) ?;
227- Some ( header_path )
246+ Some ( path )
228247 } else {
229- None
248+ // Re-opening: use user-provided header path, or None to read header from device
249+ user_header_path. clone ( )
230250 } ;
231251
232252 let devmapper_name = mapper_name. unwrap_or_else ( || {
@@ -476,6 +496,15 @@ mod tests {
476496 let mut bd = BlockDevice :: default ( ) ;
477497 let device_path = temp_device_file. path ( ) . to_string_lossy ( ) . to_string ( ) ;
478498
499+ // Header file cleaned up automatically when header_dir is dropped at
500+ // the end of the test function.
501+ let header_dir = tempfile:: TempDir :: new ( ) . unwrap ( ) ;
502+ let header_path = header_dir
503+ . path ( )
504+ . join ( "luks-header" )
505+ . to_string_lossy ( )
506+ . to_string ( ) ;
507+
479508 let options = HashMap :: from ( [
480509 ( "sourceType" . to_string ( ) , "empty" . to_string ( ) ) ,
481510 ( "targetType" . to_string ( ) , "fileSystem" . to_string ( ) ) ,
@@ -487,6 +516,7 @@ mod tests {
487516 ) ,
488517 ( "dataIntegrity" . to_string ( ) , integrity. to_string ( ) ) ,
489518 ( "filesystemType" . to_string ( ) , "ext4" . to_string ( ) ) ,
519+ ( "headerPath" . to_string ( ) , header_path. clone ( ) ) ,
490520 ] ) ;
491521
492522 let tempdir = tempfile:: TempDir :: new ( ) . unwrap ( ) ;
@@ -500,6 +530,36 @@ mod tests {
500530 . unwrap ( ) ;
501531
502532 bd. umount ( ) . await . unwrap ( ) ;
533+ drop ( bd) ;
534+
535+ // Re-open the encrypted device using the same header path (create-then-open)
536+ let tempdir2 = tempfile:: TempDir :: new ( ) . unwrap ( ) ;
537+ let mut bd2 = BlockDevice :: default ( ) ;
538+ let options_open = HashMap :: from ( [
539+ ( "sourceType" . to_string ( ) , "encrypted" . to_string ( ) ) ,
540+ ( "targetType" . to_string ( ) , "fileSystem" . to_string ( ) ) ,
541+ ( "devicePath" . to_string ( ) , device_path. clone ( ) ) ,
542+ ( "encryptionType" . to_string ( ) , "luks2" . to_string ( ) ) ,
543+ ( "dataIntegrity" . to_string ( ) , integrity. to_string ( ) ) ,
544+ (
545+ "key" . to_string ( ) ,
546+ "file://./test_files/luks2-disk-passphrase" . to_string ( ) ,
547+ ) ,
548+ ( "filesystemType" . to_string ( ) , "ext4" . to_string ( ) ) ,
549+ ( "headerPath" . to_string ( ) , header_path) ,
550+ ] ) ;
551+
552+ bd2. real_mount ( & options_open, & [ ] , tempdir2. path ( ) . to_str ( ) . unwrap ( ) )
553+ . await
554+ . unwrap ( ) ;
555+
556+ assert ! ( tempdir2. path( ) . join( "test-file" ) . exists( ) ) ;
557+ let content = tokio:: fs:: read_to_string ( tempdir2. path ( ) . join ( "test-file" ) )
558+ . await
559+ . unwrap ( ) ;
560+ assert_eq ! ( content, "some data" ) ;
561+
562+ bd2. umount ( ) . await . unwrap ( ) ;
503563 }
504564
505565 #[ cfg( feature = "luks2" ) ]
0 commit comments