From bb8d2c8a5c0a0a6510d2cc43055be21f4a3ab83c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Wei=C3=9Fe?= <66256922+daniel-weisse@users.noreply.github.com> Date: Thu, 28 Aug 2025 10:34:24 +0200 Subject: [PATCH] cryptsetup: enable detached header (#3927) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * deps: update go-cryptsetup * cryptsetup: use detached headers when opening existing crypt devices * cryptsetup: only activate disks with detached header --------- Signed-off-by: Daniel Weiße --- csi/test/BUILD.bazel | 2 + csi/test/mount_integration_test.go | 74 ++-- disk-mapper/internal/test/BUILD.bazel | 2 + disk-mapper/internal/test/integration_test.go | 43 ++- go.mod | 3 +- go.sum | 4 +- internal/cryptsetup/BUILD.bazel | 4 + internal/cryptsetup/cryptsetup.go | 281 +++++++++++--- internal/cryptsetup/cryptsetup_cgo.go | 355 +++++++++++++++++- internal/cryptsetup/cryptsetup_cross.go | 20 +- 10 files changed, 685 insertions(+), 103 deletions(-) diff --git a/csi/test/BUILD.bazel b/csi/test/BUILD.bazel index c2c5b1071..ee3d6ee5a 100644 --- a/csi/test/BUILD.bazel +++ b/csi/test/BUILD.bazel @@ -14,6 +14,7 @@ go_test( "@e2fsprogs//:bin/mkfs.ext4", "@util-linux//:bin/blkid", "@util-linux//:bin/fsck", + "@util-linux//:bin/losetup", "@util-linux//:bin/mount", "@util-linux//:bin/umount", ], @@ -23,6 +24,7 @@ go_test( "DD": "$(rlocationpath @coreutils//:bin/dd)", "FSCK": "$(rlocationpath @util-linux//:bin/fsck)", "FSCK_EXT4": "$(rlocationpath @e2fsprogs//:bin/fsck.ext4)", + "LOSETUP": "$(rlocationpath @util-linux//:bin/losetup)", "MKFS_EXT4": "$(rlocationpath @e2fsprogs//:bin/mkfs.ext4)", "MOUNT": "$(rlocationpath @util-linux//:bin/mount)", "RM": "$(rlocationpath @coreutils//:bin/rm)", diff --git a/csi/test/mount_integration_test.go b/csi/test/mount_integration_test.go index c22371c2e..90044a317 100644 --- a/csi/test/mount_integration_test.go +++ b/csi/test/mount_integration_test.go @@ -27,11 +27,11 @@ import ( ) const ( - devicePath string = "testDevice" - deviceName string = "testDeviceName" + defaultBackingImage = "testDevice" + deviceName = "testDeviceName" ) -var toolsEnvs = []string{"CP", "DD", "RM", "FSCK_EXT4", "MKFS_EXT4", "BLKID", "FSCK", "MOUNT", "UMOUNT"} +var toolsEnvs = []string{"CP", "DD", "RM", "FSCK_EXT4", "MKFS_EXT4", "BLKID", "FSCK", "MOUNT", "UMOUNT", "LOSETUP"} // addToolsToPATH is used to update the PATH to contain necessary tool binaries for // coreutils, util-linux and ext4. @@ -57,20 +57,35 @@ func addToolsToPATH() error { return nil } -func setup(devicePath string) { - if err := exec.Command("dd", "if=/dev/zero", fmt.Sprintf("of=%s", devicePath), "bs=64M", "count=1").Run(); err != nil { +func setup(backingDisk string) string { + if err := exec.Command("dd", "if=/dev/zero", fmt.Sprintf("of=%s", backingDisk), "bs=64M", "count=1").Run(); err != nil { + panic(err) + } + out, err := exec.Command("losetup", "-f", "--show", backingDisk).CombinedOutput() + if err != nil { + panic(err) + } + return strings.TrimSpace(string(out)) +} + +func teardown(backingImage, devicePath string) { + if err := exec.Command("losetup", "-d", devicePath).Run(); err != nil { + panic(err) + } + if err := exec.Command("rm", "-f", backingImage).Run(); err != nil { panic(err) } } -func teardown(devicePath string) { - if err := exec.Command("rm", "-f", devicePath).Run(); err != nil { - panic(err) +func cp(source, target string) (string, error) { + if err := exec.Command("cp", source, target).Run(); err != nil { + return "", err } -} - -func cp(source, target string) error { - return exec.Command("cp", source, target).Run() + out, err := exec.Command("losetup", "-f", "--show", target).CombinedOutput() + if err != nil { + return "", err + } + return strings.TrimSpace(string(out)), nil } func resize(devicePath string) { @@ -100,8 +115,8 @@ func TestMain(m *testing.M) { func TestOpenAndClose(t *testing.T) { assert := assert.New(t) require := require.New(t) - setup(devicePath) - defer teardown(devicePath) + devicePath := setup(defaultBackingImage) + defer teardown(defaultBackingImage, devicePath) mapper := cryptmapper.New(&fakeKMS{}) @@ -124,7 +139,7 @@ func TestOpenAndClose(t *testing.T) { assert.Equal(newPath, newPath2) // Resize the device - resize(devicePath) + resize(defaultBackingImage) resizedPath, err := mapper.ResizeCryptDevice(t.Context(), deviceName) require.NoError(err) @@ -145,8 +160,8 @@ func TestOpenAndClose(t *testing.T) { func TestOpenAndCloseIntegrity(t *testing.T) { assert := assert.New(t) require := require.New(t) - setup(devicePath) - defer teardown(devicePath) + devicePath := setup(defaultBackingImage) + defer teardown(defaultBackingImage, devicePath) mapper := cryptmapper.New(&fakeKMS{}) @@ -167,7 +182,7 @@ func TestOpenAndCloseIntegrity(t *testing.T) { assert.Equal(newPath, newPath2) // integrity devices do not support resizing - resize(devicePath) + resize(defaultBackingImage) _, err = mapper.ResizeCryptDevice(t.Context(), deviceName) assert.Error(err) @@ -182,25 +197,26 @@ func TestOpenAndCloseIntegrity(t *testing.T) { // check if we can reopen the device _, err = mapper.OpenCryptDevice(t.Context(), devicePath, deviceName, true) - assert.NoError(err) + assert.NoError(err, "Failed to re-open crypt device") assert.NoError(mapper.CloseCryptDevice(deviceName)) } func TestDeviceCloning(t *testing.T) { assert := assert.New(t) require := require.New(t) - setup(devicePath) - defer teardown(devicePath) + devicePath := setup(defaultBackingImage) + defer teardown(defaultBackingImage, devicePath) mapper := cryptmapper.New(&dynamicKMS{}) _, err := mapper.OpenCryptDevice(t.Context(), devicePath, deviceName, false) assert.NoError(err) - require.NoError(cp(devicePath, devicePath+"-copy")) - defer teardown(devicePath + "-copy") + cpDevice, err := cp(defaultBackingImage, defaultBackingImage+"-copy") + require.NoError(err) + defer teardown(defaultBackingImage+"-copy", cpDevice) - _, err = mapper.OpenCryptDevice(t.Context(), devicePath+"-copy", deviceName+"-copy", false) + _, err = mapper.OpenCryptDevice(t.Context(), cpDevice, deviceName+"-copy", false) assert.NoError(err) assert.NoError(mapper.CloseCryptDevice(deviceName)) @@ -209,12 +225,12 @@ func TestDeviceCloning(t *testing.T) { func TestConcurrency(t *testing.T) { assert := assert.New(t) - setup(devicePath) - defer teardown(devicePath) + devicePath := setup(defaultBackingImage) + defer teardown(defaultBackingImage, devicePath) - device2 := devicePath + "-2" - setup(device2) - defer teardown(device2) + backingImage2 := defaultBackingImage + "-2" + device2 := setup(backingImage2) + defer teardown(backingImage2, device2) mapper := cryptmapper.New(&fakeKMS{}) diff --git a/disk-mapper/internal/test/BUILD.bazel b/disk-mapper/internal/test/BUILD.bazel index 38e3ac89c..39e72fb1e 100644 --- a/disk-mapper/internal/test/BUILD.bazel +++ b/disk-mapper/internal/test/BUILD.bazel @@ -9,9 +9,11 @@ go_test( data = [ "@coreutils//:bin/dd", "@coreutils//:bin/rm", + "@util-linux//:bin/losetup", ], env = { "DD": "$(rlocationpath @coreutils//:bin/dd)", + "LOSETUP": "$(rlocationpath @util-linux//:bin/losetup)", "RM": "$(rlocationpath @coreutils//:bin/rm)", }, # keep diff --git a/disk-mapper/internal/test/integration_test.go b/disk-mapper/internal/test/integration_test.go index 364c97088..bda15d23f 100644 --- a/disk-mapper/internal/test/integration_test.go +++ b/disk-mapper/internal/test/integration_test.go @@ -10,6 +10,7 @@ package integration import ( "encoding/json" + "errors" "flag" "fmt" "log/slog" @@ -31,13 +32,15 @@ import ( ) const ( - devicePath = "testDevice" + backingDisk = "testDevice" mappedDevice = "mappedDevice" ) +var devicePath string + var diskPath = flag.String("disk", "", "Path to the disk to use for the benchmark") -var toolsEnvs = []string{"DD", "RM"} +var toolsEnvs = []string{"DD", "RM", "LOSETUP"} // addToolsToPATH is used to update the PATH to contain necessary tool binaries for // coreutils. @@ -64,11 +67,22 @@ func addToolsToPATH() error { } func setup(sizeGB int) error { - return exec.Command("dd", "if=/dev/random", fmt.Sprintf("of=%s", devicePath), "bs=1G", fmt.Sprintf("count=%d", sizeGB)).Run() + if err := exec.Command("dd", "if=/dev/random", fmt.Sprintf("of=%s", backingDisk), "bs=1G", fmt.Sprintf("count=%d", sizeGB)).Run(); err != nil { + return err + } + cmd := exec.Command("losetup", "-f", "--show", backingDisk) + out, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("losetup failed: %w\nOutput: %s", err, out) + } + devicePath = strings.TrimSpace(string(out)) + return nil } func teardown() error { - return exec.Command("rm", "-f", devicePath).Run() + err := exec.Command("losetup", "-d", devicePath).Run() + errors.Join(err, exec.Command("rm", "-f", backingDisk).Run()) + return err } func TestMain(m *testing.M) { @@ -137,6 +151,27 @@ func TestMapper(t *testing.T) { // Disk should still be marked as not initialized because token is set to false. assert.False(mapper.IsInitialized()) + // Set disk as initialized + assert.NoError(ccrypt.SetConstellationStateDiskToken(ccryptsetup.SetDiskInitialized)) + + // Set up a new client and check if the client still sees the disk as initialized + ccrypt2 := ccryptsetup.New() + freeDevice2, err := ccrypt2.Init(devicePath) + require.NoError(err, "failed to initialize crypt device") + defer freeDevice2() + require.NoError(ccrypt2.LoadLUKS2(), "failed to load LUKS2") + + tokenJSON, err = ccrypt2.TokenJSONGet(ccryptsetup.ConstellationStateDiskTokenID) + require.NoError(err, "token should have been set") + var token2 struct { + Type string `json:"type"` + Keyslots []string `json:"keyslots"` + DiskIsInitialized bool `json:"diskIsInitialized"` + } + require.NoError(json.Unmarshal([]byte(tokenJSON), &token2)) + assert.True(token2.DiskIsInitialized, "disk should be marked as initialized") + assert.True(ccrypt2.ConstellationStateDiskTokenIsInitialized(), "disk should be marked as initialized") + // Try to map disk with incorrect passphrase assert.Error(mapper.MapDisk(mappedDevice, "invalid-passphrase"), "was able to map disk with incorrect passphrase") diff --git a/go.mod b/go.mod index 445b01024..0c0e9b3cf 100644 --- a/go.mod +++ b/go.mod @@ -2,8 +2,7 @@ module github.com/edgelesssys/constellation/v2 go 1.24.6 -// TODO(daniel-weisse): revert after merging https://github.com/martinjungblut/go-cryptsetup/pull/16. -replace github.com/martinjungblut/go-cryptsetup => github.com/daniel-weisse/go-cryptsetup v0.0.0-20230705150314-d8c07bd1723c +replace github.com/martinjungblut/go-cryptsetup => github.com/edgelesssys/go-cryptsetup v0.0.0-20250822075033-840d240dddf8 // TODO(daniel-weisse): revert after merging https://github.com/google/go-sev-guest/pull/173. replace github.com/google/go-sev-guest => github.com/daniel-weisse/go-sev-guest v0.0.0-20250728114912-0c2ba277c52b diff --git a/go.sum b/go.sum index 57e2df68e..237c4a6fd 100644 --- a/go.sum +++ b/go.sum @@ -237,8 +237,6 @@ github.com/cyberphone/json-canonicalization v0.0.0-20220623050100-57a0ce2678a7 h github.com/cyberphone/json-canonicalization v0.0.0-20220623050100-57a0ce2678a7/go.mod h1:uzvlm1mxhHkdfqitSA92i7Se+S9ksOn3a3qmv/kyOCw= github.com/cyphar/filepath-securejoin v0.4.1 h1:JyxxyPEaktOD+GAnqIqTf9A8tHyAG22rowi7HkoSU1s= github.com/cyphar/filepath-securejoin v0.4.1/go.mod h1:Sdj7gXlvMcPZsbhwhQ33GguGLDGQL7h7bg04C/+u9jI= -github.com/daniel-weisse/go-cryptsetup v0.0.0-20230705150314-d8c07bd1723c h1:ToajP6trZoiqlZ3Z4uoG1P02/wtqSw1AcowOXOYjATk= -github.com/daniel-weisse/go-cryptsetup v0.0.0-20230705150314-d8c07bd1723c/go.mod h1:gZoZ0+POlM1ge/VUxWpMmZVNPzzMJ7l436CgkQ5+qzU= github.com/daniel-weisse/go-sev-guest v0.0.0-20250728114912-0c2ba277c52b h1:pElX9BS0PnYZS/tznradDYbo82kvG2yisWGvZGsDnVs= github.com/daniel-weisse/go-sev-guest v0.0.0-20250728114912-0c2ba277c52b/go.mod h1:SK9vW+uyfuzYdVN0m8BShL3OQCtXZe/JPF7ZkpD3760= github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs= @@ -271,6 +269,8 @@ github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 h1:UhxFibDNY/bfvqU github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7/go.mod h1:cyGadeNEkKy96OOhEzfZl+yxihPEzKnqJwvfuSUqbZE= github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff h1:V6A5kD0+c1Qg4X72Lg+zxhCZk+par436sQdgLvMCBBc= github.com/edgelesssys/go-azguestattestation v0.0.0-20250408071817-8c4457b235ff/go.mod h1:Lz4QaomI4wU2YbatD4/W7vatW2Q35tnkoJezB1clscc= +github.com/edgelesssys/go-cryptsetup v0.0.0-20250822075033-840d240dddf8 h1:aZsuG/e0UNZSttE63TplTeTpYjpl8A2GXbNQYMRUktw= +github.com/edgelesssys/go-cryptsetup v0.0.0-20250822075033-840d240dddf8/go.mod h1:gZoZ0+POlM1ge/VUxWpMmZVNPzzMJ7l436CgkQ5+qzU= github.com/edgelesssys/go-tdx-qpl v0.0.0-20250129202750-607ac61e2377 h1:5JMJiBhvOUUR7EZ0UyeSy7a1WrqB2eM+DX3odLSHAh4= github.com/edgelesssys/go-tdx-qpl v0.0.0-20250129202750-607ac61e2377/go.mod h1:IC72qyykUIWl0ZmSk53L4xbLCFDBEGZVaujUmPQOEyw= github.com/emicklei/go-restful/v3 v3.11.0 h1:rAQeMHw1c7zTmncogyy8VvRZwtkmkZ4FxERmMY4rD+g= diff --git a/internal/cryptsetup/BUILD.bazel b/internal/cryptsetup/BUILD.bazel index ddb3e9a0b..7c7531b89 100644 --- a/internal/cryptsetup/BUILD.bazel +++ b/internal/cryptsetup/BUILD.bazel @@ -16,10 +16,14 @@ go_library( visibility = ["//:__subpackages__"], deps = select({ "@io_bazel_rules_go//go/platform:android": [ + "@com_github_google_uuid//:uuid", "@com_github_martinjungblut_go_cryptsetup//:go-cryptsetup", + "@org_golang_x_sys//unix", ], "@io_bazel_rules_go//go/platform:linux": [ + "@com_github_google_uuid//:uuid", "@com_github_martinjungblut_go_cryptsetup//:go-cryptsetup", + "@org_golang_x_sys//unix", ], "//conditions:default": [], }), diff --git a/internal/cryptsetup/cryptsetup.go b/internal/cryptsetup/cryptsetup.go index 67e31825a..6caa96752 100644 --- a/internal/cryptsetup/cryptsetup.go +++ b/internal/cryptsetup/cryptsetup.go @@ -17,6 +17,7 @@ import ( "encoding/json" "errors" "fmt" + "reflect" "strings" "sync" "time" @@ -50,9 +51,18 @@ var ( // CryptSetup manages encrypted devices. type CryptSetup struct { - nameInit func(name string) (cryptDevice, error) - pathInit func(path string) (cryptDevice, error) - device cryptDevice + nameInit func(name string) (deviceDetachedHeader, deviceAttachedHeader cryptDevice, headerDevice string, headerFile string, err error) + pathInit func(path string) (deviceDetachedHeader, deviceAttachedHeader cryptDevice, headerDevice string, headerFile string, err error) + // deviceWithDetachedHeader is the cryptsetup device with detached header we are working on. + deviceWithDetachedHeader cryptDevice + // deviceWithAttachedHeader is a cryptsetup device loaded without a separate, detached header. + // If this is not a fresh disk, this device is purely used to write back changes that affect the header to the original disk. + deviceWithAttachedHeader cryptDevice + // headerDevice is the name of the loopback device containing the detached cryptsetup header. + headerDevice string + // headerFile is the path to the file containing the detached cryptsetup header. + // The file is mounted as a loopback device on "headerDevice". + headerFile string } // New creates a new CryptSetup. @@ -68,14 +78,17 @@ func New() *CryptSetup { func (c *CryptSetup) Init(devicePath string) (free func(), err error) { packageLock.Lock() defer packageLock.Unlock() - if c.device != nil { + if c.hasDetachedHeaderDevice() || c.hasAttachedHeaderDevice() { return nil, errDeviceAlreadyOpen } - device, err := c.pathInit(devicePath) + deviceDetachedHeader, deviceAttachedHeader, headerDevice, headerFile, err := c.pathInit(devicePath) if err != nil { return nil, fmt.Errorf("init cryptsetup by device path %q: %w", devicePath, err) } - c.device = device + c.deviceWithDetachedHeader = deviceDetachedHeader + c.deviceWithAttachedHeader = deviceAttachedHeader + c.headerDevice = headerDevice + c.headerFile = headerFile return c.Free, nil } @@ -83,14 +96,17 @@ func (c *CryptSetup) Init(devicePath string) (free func(), err error) { func (c *CryptSetup) InitByName(name string) (free func(), err error) { packageLock.Lock() defer packageLock.Unlock() - if c.device != nil { + if c.hasDetachedHeaderDevice() || c.hasAttachedHeaderDevice() { return nil, errDeviceAlreadyOpen } - device, err := c.nameInit(name) + deviceDetachedHeader, deviceAttachedHeader, headerDevice, headerFile, err := c.nameInit(name) if err != nil { return nil, fmt.Errorf("init cryptsetup by name %q: %w", name, err) } - c.device = device + c.deviceWithDetachedHeader = deviceDetachedHeader + c.deviceWithAttachedHeader = deviceAttachedHeader + c.headerDevice = headerDevice + c.headerFile = headerFile return c.Free, nil } @@ -98,20 +114,19 @@ func (c *CryptSetup) InitByName(name string) (free func(), err error) { func (c *CryptSetup) Free() { packageLock.Lock() defer packageLock.Unlock() - if c.device != nil { - c.device.Free() - c.device = nil - } + c.free() } // ActivateByPassphrase actives a crypt device using a passphrase. func (c *CryptSetup) ActivateByPassphrase(deviceName string, keyslot int, passphrase string, flags int) error { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen + if !c.hasDetachedHeaderDevice() { + if err := c.reload(); err != nil { + return fmt.Errorf("re-loading crypt device for activation: %w", err) + } } - if err := c.device.ActivateByPassphrase(deviceName, keyslot, passphrase, flags); err != nil { + if err := c.deviceWithDetachedHeader.ActivateByPassphrase(deviceName, keyslot, passphrase, flags); err != nil { return fmt.Errorf("activating crypt device %q using passphrase: %w", deviceName, err) } return nil @@ -122,10 +137,12 @@ func (c *CryptSetup) ActivateByPassphrase(deviceName string, keyslot int, passph func (c *CryptSetup) ActivateByVolumeKey(deviceName, volumeKey string, volumeKeySize, flags int) error { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen + if !c.hasDetachedHeaderDevice() { + if err := c.reload(); err != nil { + return fmt.Errorf("re-loading crypt device for activation: %w", err) + } } - if err := c.device.ActivateByVolumeKey(deviceName, volumeKey, volumeKeySize, flags); err != nil { + if err := c.deviceWithDetachedHeader.ActivateByVolumeKey(deviceName, volumeKey, volumeKeySize, flags); err != nil { return fmt.Errorf("activating crypt device %q using volume key: %w", deviceName, err) } return nil @@ -135,10 +152,10 @@ func (c *CryptSetup) ActivateByVolumeKey(deviceName, volumeKey string, volumeKey func (c *CryptSetup) Deactivate(deviceName string) error { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { + if !c.hasDetachedHeaderDevice() { return errDeviceNotOpen } - if err := c.device.Deactivate(deviceName); err != nil { + if err := c.deviceWithDetachedHeader.Deactivate(deviceName); err != nil { return fmt.Errorf("deactivating crypt device %q: %w", deviceName, err) } return nil @@ -149,18 +166,42 @@ func (c *CryptSetup) Deactivate(deviceName string) error { func (c *CryptSetup) Format(integrity bool) error { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { + var device cryptDevice + + // If we are re-formatting an existing device, we start from scratch without a detached header + if c.hasAttachedHeaderDevice() { + if c.deviceWithDetachedHeader != nil { + c.deviceWithDetachedHeader.Free() + c.deviceWithDetachedHeader = nil + } + if c.headerDevice != "" { + _ = detachLoopbackDevice(c.headerDevice) + c.headerDevice = "" + } + c.headerFile = "" + device = c.deviceWithAttachedHeader + } else { return errDeviceNotOpen } - if err := format(c.device, integrity); err != nil { - return fmt.Errorf("formatting crypt device %q: %w", c.device.GetDeviceName(), err) + + if err := format(device, integrity); err != nil { + return fmt.Errorf("formatting crypt device %q: %w", device.GetDeviceName(), err) + } + if err := c.createHeaderBackup(); err != nil { + return err } return nil } // GetDeviceName gets the path to the underlying device. func (c *CryptSetup) GetDeviceName() string { - return c.device.GetDeviceName() + packageLock.Lock() + defer packageLock.Unlock() + device, err := c.getActiveDevice() + if err != nil { + return "" + } + return device.GetDeviceName() } // GetUUID gets the device's LUKS2 UUID. @@ -168,12 +209,13 @@ func (c *CryptSetup) GetDeviceName() string { func (c *CryptSetup) GetUUID() (string, error) { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return "", errDeviceNotOpen + device, err := c.getActiveDevice() + if err != nil { + return "", err } - uuid := c.device.GetUUID() + uuid := device.GetUUID() if uuid == "" { - return "", fmt.Errorf("unable to get UUID for device %q", c.device.GetDeviceName()) + return "", fmt.Errorf("unable to get UUID for device %q", device.GetDeviceName()) } return strings.ToLower(uuid), nil } @@ -183,11 +225,15 @@ func (c *CryptSetup) GetUUID() (string, error) { func (c *CryptSetup) KeyslotAddByVolumeKey(keyslot int, volumeKey string, passphrase string) error { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen + device, err := c.getActiveDevice() + if err != nil { + return err } - if err := c.device.KeyslotAddByVolumeKey(keyslot, volumeKey, passphrase); err != nil { - return fmt.Errorf("adding keyslot to device %q: %w", c.device.GetDeviceName(), err) + if err := device.KeyslotAddByVolumeKey(keyslot, volumeKey, passphrase); err != nil { + return fmt.Errorf("adding keyslot to device %q: %w", device.GetDeviceName(), err) + } + if err := c.createHeaderBackup(); err != nil { + return err } return nil } @@ -196,21 +242,30 @@ func (c *CryptSetup) KeyslotAddByVolumeKey(keyslot int, volumeKey string, passph func (c *CryptSetup) KeyslotChangeByPassphrase(currentKeyslot, newKeyslot int, currentPassphrase, newPassphrase string) error { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen + device, err := c.getActiveDevice() + if err != nil { + return err } - if err := c.device.KeyslotChangeByPassphrase(currentKeyslot, newKeyslot, currentPassphrase, newPassphrase); err != nil { - return fmt.Errorf("updating passphrase for device %q: %w", c.device.GetDeviceName(), err) + if err := device.KeyslotChangeByPassphrase(currentKeyslot, newKeyslot, currentPassphrase, newPassphrase); err != nil { + return fmt.Errorf("updating passphrase for device %q: %w", device.GetDeviceName(), err) + } + if err := c.createHeaderBackup(); err != nil { + return err } return nil } // LoadLUKS2 loads the device as LUKS2 crypt device. func (c *CryptSetup) LoadLUKS2() error { - if err := loadLUKS2(c.device); err != nil { - return fmt.Errorf("loading LUKS2 crypt device %q: %w", c.device.GetDeviceName(), err) + packageLock.Lock() + defer packageLock.Unlock() + if c.hasDetachedHeaderDevice() { + if err := loadLUKS2(c.deviceWithDetachedHeader); err != nil { + return fmt.Errorf("loading LUKS2 crypt device %q: %w", c.deviceWithDetachedHeader.GetDeviceName(), err) + } + return nil } - return nil + return errors.New("cannot load LUKS2 on device with attached header") } // Resize resizes a device to the given size. @@ -219,11 +274,15 @@ func (c *CryptSetup) LoadLUKS2() error { func (c *CryptSetup) Resize(name string, newSize uint64) error { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen + device, err := c.getActiveDevice() + if err != nil { + return err } - if err := c.device.Resize(name, newSize); err != nil { - return fmt.Errorf("resizing crypt device %q: %w", c.device.GetDeviceName(), err) + if err := device.Resize(name, newSize); err != nil { + return fmt.Errorf("resizing crypt device %q: %w", device.GetDeviceName(), err) + } + if err := c.createHeaderBackup(); err != nil { + return err } return nil } @@ -232,10 +291,11 @@ func (c *CryptSetup) Resize(name string, newSize uint64) error { func (c *CryptSetup) TokenJSONGet(token int) (string, error) { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return "", errDeviceNotOpen + device, err := c.getActiveDevice() + if err != nil { + return "", err } - json, err := c.device.TokenJSONGet(token) + json, err := device.TokenJSONGet(token) if err != nil { return "", fmt.Errorf("getting JSON data for token %d: %w", token, err) } @@ -252,18 +312,25 @@ func (c *CryptSetup) TokenJSONGet(token int) (string, error) { func (c *CryptSetup) TokenJSONSet(token int, json string) (int, error) { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return -1, errDeviceNotOpen + device, err := c.getActiveDevice() + if err != nil { + return -1, err } - tokenID, err := c.device.TokenJSONSet(token, json) + + tokenID, err := device.TokenJSONSet(token, json) if err != nil { return -1, fmt.Errorf("setting JSON data for token %d: %w", token, err) } + if err := c.createHeaderBackup(); err != nil { + return -1, err + } return tokenID, nil } // SetConstellationStateDiskToken sets the Constellation state disk token. func (c *CryptSetup) SetConstellationStateDiskToken(diskIsInitialized bool) error { + packageLock.Lock() + defer packageLock.Unlock() token := constellationLUKS2Token{ Type: "constellation-state-disk", Keyslots: []string{}, @@ -273,15 +340,31 @@ func (c *CryptSetup) SetConstellationStateDiskToken(diskIsInitialized bool) erro if err != nil { return fmt.Errorf("marshaling token: %w", err) } - if _, err := c.device.TokenJSONSet(ConstellationStateDiskTokenID, string(json)); err != nil { + + device, err := c.getActiveDevice() + if err != nil { + return err + } + + if _, err := device.TokenJSONSet(ConstellationStateDiskTokenID, string(json)); err != nil { return fmt.Errorf("setting token: %w", err) } + if err := c.createHeaderBackup(); err != nil { + return err + } return nil } // ConstellationStateDiskTokenIsInitialized returns true if the Constellation state disk token is set to initialized. func (c *CryptSetup) ConstellationStateDiskTokenIsInitialized() bool { - stateDiskToken, err := c.device.TokenJSONGet(ConstellationStateDiskTokenID) + packageLock.Lock() + defer packageLock.Unlock() + device, err := c.getActiveDevice() + if err != nil { + return false + } + + stateDiskToken, err := device.TokenJSONGet(ConstellationStateDiskTokenID) if err != nil { return false } @@ -298,17 +381,18 @@ func (c *CryptSetup) Wipe( ) (err error) { packageLock.Lock() defer packageLock.Unlock() - if c.device == nil { - return errDeviceNotOpen + device, err := c.getActiveDevice() + if err != nil { + return err } // Active temporary device to perform wipe on tmpDevice := tmpDevicePrefix + name - if err := c.device.ActivateByVolumeKey(tmpDevice, "", 0, wipeFlags); err != nil { + if err := device.ActivateByVolumeKey(tmpDevice, "", 0, wipeFlags); err != nil { return fmt.Errorf("trying to activate temporary dm-crypt volume: %w", err) } defer func() { - if deactivateErr := c.device.Deactivate(tmpDevice); deactivateErr != nil { + if deactivateErr := device.Deactivate(tmpDevice); deactivateErr != nil { err = errors.Join(err, fmt.Errorf("deactivating temporary device %q: %w", tmpDevice, deactivateErr)) } }() @@ -330,8 +414,89 @@ func (c *CryptSetup) Wipe( return 0 } - if err := c.device.Wipe(mappedDevicePath+tmpDevice, wipePattern, 0, 0, blockWipeSize, flags, progressCallback); err != nil { - return fmt.Errorf("wiping disk of device %q: %w", c.device.GetDeviceName(), err) + if err := device.Wipe(mappedDevicePath+tmpDevice, wipePattern, 0, 0, blockWipeSize, flags, progressCallback); err != nil { + return fmt.Errorf("wiping disk of device %q: %w", device.GetDeviceName(), err) + } + if err := c.createHeaderBackup(); err != nil { + return err + } + return nil +} + +func (c *CryptSetup) free() { + if c.hasDetachedHeaderDevice() { + c.deviceWithDetachedHeader.Free() + c.deviceWithDetachedHeader = nil + } + if c.hasAttachedHeaderDevice() { + c.deviceWithAttachedHeader.Free() + c.deviceWithAttachedHeader = nil + } + if c.headerDevice != "" { + _ = detachLoopbackDevice(c.headerDevice) + } + if c.headerFile != "" { + c.headerFile = "" + } +} + +func (c *CryptSetup) reload() error { + if !c.hasAttachedHeaderDevice() { + return errDeviceNotOpen + } + + backingDevice := c.deviceWithAttachedHeader.GetDeviceName() + c.free() + var err error + c.deviceWithDetachedHeader, c.deviceWithAttachedHeader, c.headerDevice, c.headerFile, err = c.pathInit(backingDevice) + if err != nil { + return fmt.Errorf("re-loading crypt device: %w", err) + } + + if !c.hasDetachedHeaderDevice() { + return errors.New("failed to reload device without detached header") + } + + if err := loadLUKS2(c.deviceWithDetachedHeader); err != nil { + return err + } + + return nil +} + +// getActiveDevice returns a handle to the active cryptsetup device with detached header if set, +// or one with attached header otherwise. +func (c *CryptSetup) getActiveDevice() (cryptDevice, error) { + if c.hasDetachedHeaderDevice() { + return c.deviceWithDetachedHeader, nil + } + if c.hasAttachedHeaderDevice() { + return c.deviceWithAttachedHeader, nil + } + return nil, errDeviceNotOpen +} + +// hasDetachedHeaderDevice checks if the value of the [CryptSetup.deviceWithDetachedHeader] interface is not nil. +func (c *CryptSetup) hasDetachedHeaderDevice() bool { + return c.deviceWithDetachedHeader != nil && !reflect.ValueOf(c.deviceWithDetachedHeader).IsNil() +} + +// hasAttachedHeaderDevice checks if the value of the [CryptSetup.deviceWithAttachedHeader] interface is not nil. +func (c *CryptSetup) hasAttachedHeaderDevice() bool { + return c.deviceWithAttachedHeader != nil && !reflect.ValueOf(c.deviceWithAttachedHeader).IsNil() +} + +// createHeaderBackup creates a backup of the detached header, and saves it back to the original device. +func (c *CryptSetup) createHeaderBackup() error { + if c.hasDetachedHeaderDevice() && c.headerFile != "" { + if err := headerBackup(c.deviceWithDetachedHeader, c.headerFile); err != nil { + return fmt.Errorf("creating header backup for device %q: %w", c.deviceWithDetachedHeader.GetDeviceName(), err) + } + } + if c.hasAttachedHeaderDevice() && c.headerFile != "" { + if err := headerRestore(c.deviceWithAttachedHeader, c.headerFile); err != nil { + return fmt.Errorf("restoring header for device %q (with attached header): %w", c.deviceWithDetachedHeader.GetDeviceName(), err) + } } return nil } diff --git a/internal/cryptsetup/cryptsetup_cgo.go b/internal/cryptsetup/cryptsetup_cgo.go index e8ac2e31a..f85c7cc53 100644 --- a/internal/cryptsetup/cryptsetup_cgo.go +++ b/internal/cryptsetup/cryptsetup_cgo.go @@ -11,9 +11,16 @@ package cryptsetup import "C" import ( + "encoding/json" "errors" + "fmt" + "os" + "path/filepath" + "strings" + "github.com/google/uuid" "github.com/martinjungblut/go-cryptsetup" + "golang.org/x/sys/unix" ) const ( @@ -56,12 +63,103 @@ func format(device cryptDevice, integrity bool) error { } } -func initByDevicePath(devicePath string) (cryptDevice, error) { - return cryptsetup.Init(devicePath) +// headerRestore restores the header of the given device from the header in the given file. +// Reloading the device is required for the changes to be reflected in the active [cryptDevice] struct. +func headerRestore(device cryptDevice, headerFile string) error { + switch d := device.(type) { + case cgoRestorer: + return d.HeaderRestore(cryptsetup.LUKS2{}, headerFile) + default: + return errInvalidType + } } -func initByName(name string) (cryptDevice, error) { - return cryptsetup.InitByName(name) +// headerBackup creates a backup of the cryptDevice's header to the given file. +func headerBackup(device cryptDevice, headerFile string) error { + switch d := device.(type) { + case cgoBackuper: + if err := os.Remove(headerFile); err != nil && !errors.Is(err, os.ErrNotExist) { + return fmt.Errorf("removing existing header file %q: %w", headerFile, err) + } + if err := d.HeaderBackup(cryptsetup.LUKS2{}, headerFile); err != nil { + return fmt.Errorf("creating header backup: %w", err) + } + return nil + default: + return errInvalidType + } +} + +func initByDevicePath(devicePath string) (deviceDetachedHeader, deviceAttachedHeader cryptDevice, headerDevice string, headerFile string, err error) { + tmpDevice, err := cryptsetup.Init(devicePath) + if err != nil { + return nil, nil, "", "", fmt.Errorf("init device by path %s: %w", devicePath, err) + } + // If the device is not LUKS2 formatted, this is treated as a new device, + // meaning no header exists yet + if err := tmpDevice.Load(cryptsetup.LUKS2{}); err != nil { + return nil, tmpDevice, "", "", nil + } + defer tmpDevice.Free() + + deviceAttachedHeader, err = cryptsetup.Init(devicePath) + if err != nil { + return nil, nil, "", "", fmt.Errorf("init device by path %s: %w", devicePath, err) + } + defer func() { + if err != nil && deviceAttachedHeader != nil { + deviceAttachedHeader.Free() + } + }() + + headerDevice, headerFile, err = detachHeader(tmpDevice) + if err != nil { + return nil, nil, "", "", err + } + defer func() { + if err != nil { + _ = detachLoopbackDevice(headerDevice) + } + }() + + cryptDevice, err := cryptsetup.InitDataDevice(headerDevice, devicePath) + return cryptDevice, deviceAttachedHeader, headerDevice, headerFile, err +} + +func initByName(name string) (deviceDetachedHeader, deviceAttachedHeader cryptDevice, headerDevice string, headerFile string, err error) { + tmpDevice, err := cryptsetup.InitByName(name) + if err != nil { + return nil, nil, "", "", fmt.Errorf("init device by name %s: %w", name, err) + } + // If the device is not LUKS2 formatted, this is treated as a new device, + // meaning no header exists yet + if err := tmpDevice.Load(cryptsetup.LUKS2{}); err != nil { + return nil, tmpDevice, "", "", nil + } + defer tmpDevice.Free() + + deviceAttachedHeader, err = cryptsetup.InitByName(name) + if err != nil { + return nil, nil, "", "", fmt.Errorf("init device by name %s: %w", name, err) + } + defer func() { + if err != nil && deviceAttachedHeader != nil { + deviceAttachedHeader.Free() + } + }() + + headerDevice, headerFile, err = detachHeader(tmpDevice) + if err != nil { + return nil, nil, "", "", err + } + defer func() { + if err != nil { + _ = detachLoopbackDevice(headerDevice) + } + }() + + cryptDevice, err := cryptsetup.InitByNameAndHeader(name, headerDevice) + return cryptDevice, deviceAttachedHeader, headerDevice, headerFile, err } func loadLUKS2(device cryptDevice) error { @@ -73,6 +171,248 @@ func loadLUKS2(device cryptDevice) error { } } +// detachHeader loads reads the header from the given cryptsetup device and returns a loopback device with just the header. +func detachHeader(device *cryptsetup.Device) (headerDevice, headerFile string, err error) { + headerFile = filepath.Join(os.TempDir(), fmt.Sprintf("luks-header-%s", uuid.New().String())) + if err = headerBackup(device, headerFile); err != nil { + return "", "", err + } + + headerDevice, err = createLoopbackDevice(headerFile) + if err != nil { + return "", "", fmt.Errorf("create loopback device: %w", err) + } + defer func() { + if err != nil { + _ = detachLoopbackDevice(headerDevice) + } + }() + + headerCryptDevice, err := cryptsetup.Init(headerDevice) + if err != nil { + return "", "", fmt.Errorf("init header device: %w", err) + } + defer headerCryptDevice.Free() + if err := headerCryptDevice.Load(cryptsetup.LUKS2{}); err != nil { + return "", "", fmt.Errorf("creating header backup: %w", err) + } + metadataJSON, err := headerCryptDevice.DumpJSON() + if err != nil { + return "", "", fmt.Errorf("dumping device metadata: %w", err) + } + + var metadata cryptsetupMetadata + decoder := json.NewDecoder(strings.NewReader(metadataJSON)) + decoder.DisallowUnknownFields() // Ensure no unknown fields are present in the JSON data + if err := decoder.Decode(&metadata); err != nil { + return "", "", fmt.Errorf("decoding LUKS header JSON from %s: %w", headerFile, err) + } + + if err := verifyLUKS2Header(metadata); err != nil { + return "", "", fmt.Errorf("verifying LUKS2 header: %w", err) + } + + return headerDevice, headerFile, nil +} + +// verifyLUKS2Header verifies a LUKS2 header contains the expected configuration for Constellation. +func verifyLUKS2Header(metadata cryptsetupMetadata) error { + if len(metadata.KeySlots) == 0 { + return errors.New("no key slots found in LUKS2 header") + } + for slotName, slot := range metadata.KeySlots { + if slot.Type != "luks2" { + return fmt.Errorf("unsupported key slot type %q for slot %q", slot.Type, slotName) + } + if slot.KeySize != 64 && slot.KeySize != 96 { // 64 for encryption, 96 if integrity is added + return fmt.Errorf("unsupported key size %d for slot %q", slot.KeySize, slotName) + } + if slot.AntiForensicSplitter.Type != "luks1" { + return fmt.Errorf("unsupported anti-forensic splitter type %q for slot %q", slot.AntiForensicSplitter.Type, slotName) + } + if slot.AntiForensicSplitter.Stripes != 4000 { + return fmt.Errorf("unsupported anti-forensic splitter stripes %d for slot %q", slot.AntiForensicSplitter.Stripes, slotName) + } + if slot.AntiForensicSplitter.Hash != "sha256" { + return fmt.Errorf("unsupported anti-forensic splitter hash %q for slot %q", slot.AntiForensicSplitter.Hash, slotName) + } + if slot.Area.Type != "raw" { + return fmt.Errorf("unsupported area type %q for slot %q", slot.Area.Type, slotName) + } + if slot.Area.Encryption != "aes-xts-plain64" { + return fmt.Errorf("unsupported area encryption %q for slot %q", slot.Area.Encryption, slotName) + } + if slot.Area.KeySize != 64 { + return fmt.Errorf("unsupported area key size %d for slot %q", slot.Area.KeySize, slotName) + } + if slot.KDF.Type != "argon2id" { + return fmt.Errorf("unsupported KDF type %q for slot %q", slot.KDF.Type, slotName) + } + if slot.KDF.Memory == 0 { + return fmt.Errorf("unsupported KDF memory %d for slot %q", slot.KDF.Memory, slotName) + } + if slot.KDF.Salt == "" { + return fmt.Errorf("unsupported KDF salt for slot %q", slotName) + } + } + if len(metadata.Segments) == 0 { + return errors.New("no segments found in LUKS2 header") + } + for segmentName, segment := range metadata.Segments { + if segment.Type != "crypt" { + return fmt.Errorf("unsupported segment type %q for segment %q", segment.Type, segmentName) + } + if segment.SectorSize != 4096 { + return fmt.Errorf("unsupported segment sector size %d for segment %q", segment.SectorSize, segmentName) + } + if segment.IVTweak != "0" { + return fmt.Errorf("unsupported segment IV tweak %q for segment %q", segment.IVTweak, segmentName) + } + if segment.Encryption != "aes-xts-plain64" { + return fmt.Errorf("unsupported segment encryption %q for segment %q", segment.Encryption, segmentName) + } + switch segment.Integrity.Type { + case "hmac(sha256)": + if segment.Integrity.JournalEncryption != "none" { + return fmt.Errorf("unsupported segment integrity journal encryption %q for segment %q", segment.Integrity.JournalEncryption, segmentName) + } + if segment.Integrity.JournalIntegrity != "none" { + return fmt.Errorf("unsupported segment integrity journal integrity %q for segment %q", segment.Integrity.JournalIntegrity, segmentName) + } + case "": + if segment.Integrity.JournalEncryption != "" { + return fmt.Errorf("unsupported segment integrity journal encryption %q for segment %q", segment.Integrity.JournalEncryption, segmentName) + } + if segment.Integrity.JournalIntegrity != "" { + return fmt.Errorf("unsupported segment integrity journal integrity %q for segment %q", segment.Integrity.JournalIntegrity, segmentName) + } + default: + return fmt.Errorf("unsupported segment integrity type %q for segment %q", segment.Integrity.Type, segmentName) + } + } + if len(metadata.Digests) == 0 { + return errors.New("no digests found in LUKS2 header") + } + for digestName, digest := range metadata.Digests { + if digest.Type != "pbkdf2" { + return fmt.Errorf("unsupported digest type %q for digest %q", digest.Type, digestName) + } + if digest.Hash != "sha256" { + return fmt.Errorf("unsupported digest hash %q for digest %q", digest.Hash, digestName) + } + if digest.Salt == "" { + return fmt.Errorf("unsupported digest salt for digest %q", digestName) + } + if digest.Digest == "" { + return fmt.Errorf("unsupported digest value for digest %q", digestName) + } + } + return nil +} + +// createLoopbackDevice sets up a loop device for the given file and returns the loop device path (e.g., /dev/loop0). +func createLoopbackDevice(filePath string) (string, error) { + file, err := os.OpenFile(filePath, os.O_RDWR, 0) + if err != nil { + return "", fmt.Errorf("open backing file: %w", err) + } + defer file.Close() + + // Get a free loop device number + ctrl, err := os.OpenFile("/dev/loop-control", os.O_RDWR, 0) + if err != nil { + return "", fmt.Errorf("open /dev/loop-control: %w", err) + } + defer ctrl.Close() + loopNum, _, errno := unix.Syscall(unix.SYS_IOCTL, ctrl.Fd(), unix.LOOP_CTL_GET_FREE, 0) + if errno != 0 { + return "", fmt.Errorf("LOOP_CTL_GET_FREE: %v", errno) + } + + // Open the loop device + loopDev := fmt.Sprintf("/dev/loop%d", loopNum) + loop, err := os.OpenFile(loopDev, os.O_RDWR, 0) + if err != nil { + return "", fmt.Errorf("open loop device: %w", err) + } + defer loop.Close() + + // Associate the file with the loop device + if _, _, errno := unix.Syscall(unix.SYS_IOCTL, loop.Fd(), unix.LOOP_SET_FD, file.Fd()); errno != 0 { + return "", fmt.Errorf("LOOP_SET_FD: %v", errno) + } + + return loopDev, nil +} + +// detachLoopbackDevice removes the specified loopback device. +func detachLoopbackDevice(loopDev string) error { + loop, err := os.OpenFile(loopDev, os.O_RDWR, 0) + if err != nil { + return fmt.Errorf("open loop device: %w", err) + } + defer loop.Close() + + if _, _, errno := unix.Syscall(unix.SYS_IOCTL, loop.Fd(), unix.LOOP_CLR_FD, 0); errno != 0 { + return fmt.Errorf("LOOP_CLR_FD: %v", errno) + } + return nil +} + +type cryptsetupMetadata struct { + KeySlots map[string]struct { + Type string `json:"type"` + KeySize int `json:"key_size"` + AntiForensicSplitter struct { + Type string `json:"type"` + Stripes int `json:"stripes"` + Hash string `json:"hash"` + } `json:"af"` + Area struct { + Type string `json:"type"` + Offset string `json:"offset"` + Size string `json:"size"` + Encryption string `json:"encryption"` + KeySize int `json:"key_size"` + } `json:"area"` + KDF struct { + Type string `json:"type"` + Time int `json:"time"` + Memory int `json:"memory"` + CPUs int `json:"cpus"` + Salt string `json:"salt"` + } `json:"kdf"` + } `json:"keyslots"` + Tokens map[string]any `json:"tokens"` + Segments map[string]struct { + Type string `json:"type"` + Offset string `json:"offset"` + Size string `json:"size"` + Flags []string `json:"flags,omitempty"` + IVTweak string `json:"iv_tweak"` + Encryption string `json:"encryption"` + SectorSize int `json:"sector_size"` + Integrity struct { + Type string `json:"type"` + JournalEncryption string `json:"journal_encryption"` + JournalIntegrity string `json:"journal_integrity"` + } + } `json:"segments"` + Digests map[string]struct { + Type string `json:"type"` + Keyslots []string `json:"keyslots"` + Segments []string `json:"segments"` + Hash string `json:"hash"` + Iterations int `json:"iterations"` + Salt string `json:"salt"` + Digest string `json:"digest"` + } `json:"digests"` + Config struct { + JSONSize string `json:"json_size"` + KeyslotsSize string `json:"keyslots_size"` + } +} + type cgoFormatter interface { Format(deviceType cryptsetup.DeviceType, genericParams cryptsetup.GenericParams) error } @@ -80,3 +420,10 @@ type cgoFormatter interface { type cgoLoader interface { Load(deviceType cryptsetup.DeviceType) error } + +type cgoRestorer interface { + HeaderRestore(deviceType cryptsetup.DeviceType, headerFile string) error +} +type cgoBackuper interface { + HeaderBackup(deviceType cryptsetup.DeviceType, headerFile string) error +} diff --git a/internal/cryptsetup/cryptsetup_cross.go b/internal/cryptsetup/cryptsetup_cross.go index 325a86be4..e7765963f 100644 --- a/internal/cryptsetup/cryptsetup_cross.go +++ b/internal/cryptsetup/cryptsetup_cross.go @@ -26,14 +26,26 @@ func format(_ cryptDevice, _ bool) error { return errCGONotSupported } -func initByDevicePath(_ string) (cryptDevice, error) { - return nil, errCGONotSupported +func headerRestore(_ cryptDevice, _ string) error { + return errCGONotSupported } -func initByName(_ string) (cryptDevice, error) { - return nil, errCGONotSupported +func headerBackup(_ cryptDevice, _ string) error { + return errCGONotSupported +} + +func initByDevicePath(_ string) (cryptDevice, cryptDevice, string, string, error) { + return nil, nil, "", "", errCGONotSupported +} + +func initByName(_ string) (cryptDevice, cryptDevice, string, string, error) { + return nil, nil, "", "", errCGONotSupported } func loadLUKS2(_ cryptDevice) error { return errCGONotSupported } + +func detachLoopbackDevice(_ string) error { + return errCGONotSupported +}