From 04a98510c07fe8477f598befbfe6eaec4f4b73a2 Mon Sep 17 00:00:00 2001 From: Artem Chernyshev Date: Fri, 29 Jan 2021 17:31:23 +0300 Subject: [PATCH] feat: implement luks encryption provider Fixes: https://github.com/talos-systems/talos/issues/3030 Wrap around `cryptsetup` module and support encrypt, open and close functions as a start. Signed-off-by: Artem Chernyshev --- blockdevice/blockdevice.go | 4 +- blockdevice/blockdevice_darwin.go | 2 +- blockdevice/blockdevice_linux.go | 5 +- blockdevice/encryption/encryption.go | 6 + blockdevice/encryption/luks/luks.go | 284 +++++++++++++++++++ blockdevice/encryption/luks/luks_test.go | 163 +++++++++++ blockdevice/encryption/luks/options.go | 32 +++ blockdevice/encryption/provider.go | 62 ++++ blockdevice/filesystem/fs.go | 3 + blockdevice/filesystem/iso9660/superblock.go | 5 + blockdevice/filesystem/luks/doc.go | 6 + blockdevice/filesystem/luks/superblock.go | 55 ++++ blockdevice/filesystem/vfat/superblock.go | 5 + blockdevice/filesystem/xfs/superblock.go | 5 + blockdevice/partition/gpt/partition.go | 52 +++- blockdevice/probe/probe_test.go | 5 +- blockdevice/util/util.go | 5 + 17 files changed, 683 insertions(+), 16 deletions(-) create mode 100644 blockdevice/encryption/encryption.go create mode 100644 blockdevice/encryption/luks/luks.go create mode 100644 blockdevice/encryption/luks/luks_test.go create mode 100644 blockdevice/encryption/luks/options.go create mode 100644 blockdevice/encryption/provider.go create mode 100644 blockdevice/filesystem/luks/doc.go create mode 100644 blockdevice/filesystem/luks/superblock.go diff --git a/blockdevice/blockdevice.go b/blockdevice/blockdevice.go index 1cdcfa7..0cfa80f 100644 --- a/blockdevice/blockdevice.go +++ b/blockdevice/blockdevice.go @@ -5,7 +5,9 @@ // Package blockdevice provides a library for working with block devices. package blockdevice -import "errors" +import ( + "errors" +) // ErrMissingPartitionTable indicates that the the block device does not have a // partition table. diff --git a/blockdevice/blockdevice_darwin.go b/blockdevice/blockdevice_darwin.go index c591e3c..1585c7e 100644 --- a/blockdevice/blockdevice_darwin.go +++ b/blockdevice/blockdevice_darwin.go @@ -65,7 +65,7 @@ func (bd *BlockDevice) Wipe() error { } // OpenPartition opens another blockdevice using a partition of this block device. -func (bd *BlockDevice) OpenPartition(label string) (*BlockDevice, error) { +func (bd *BlockDevice) OpenPartition(label string, setters ...Option) (*BlockDevice, error) { return nil, fmt.Errorf("not implemented") } diff --git a/blockdevice/blockdevice_linux.go b/blockdevice/blockdevice_linux.go index 8862387..d0c56d4 100644 --- a/blockdevice/blockdevice_linux.go +++ b/blockdevice/blockdevice_linux.go @@ -45,7 +45,6 @@ type BlockDevice struct { // TODO(andrewrynhard): Use BLKGETSIZE ioctl to get the size. func Open(devname string, setters ...Option) (bd *BlockDevice, err error) { opts := NewDefaultOptions(setters...) - bd = &BlockDevice{} var f *os.File @@ -274,7 +273,7 @@ func (bd *BlockDevice) Reset() error { } // OpenPartition opens another blockdevice using a partition of this block device. -func (bd *BlockDevice) OpenPartition(label string) (*BlockDevice, error) { +func (bd *BlockDevice) OpenPartition(label string, setters ...Option) (*BlockDevice, error) { g, err := bd.PartitionTable() if err != nil { return nil, err @@ -290,7 +289,7 @@ func (bd *BlockDevice) OpenPartition(label string) (*BlockDevice, error) { return nil, err } - return Open(path) + return Open(path, setters...) } // GetPartition returns partition by label if found. diff --git a/blockdevice/encryption/encryption.go b/blockdevice/encryption/encryption.go new file mode 100644 index 0000000..784ff22 --- /dev/null +++ b/blockdevice/encryption/encryption.go @@ -0,0 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package encryption provides abstraction level for various disk encryption methods. +package encryption diff --git a/blockdevice/encryption/luks/luks.go b/blockdevice/encryption/luks/luks.go new file mode 100644 index 0000000..96bedb9 --- /dev/null +++ b/blockdevice/encryption/luks/luks.go @@ -0,0 +1,284 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package luks provides a way to call LUKS2 cryptsetup. +package luks + +import ( + "bytes" + "encoding/binary" + "encoding/json" + "fmt" + "os" + "os/exec" + "strings" + "time" + + "golang.org/x/sys/unix" + + "github.com/talos-systems/go-blockdevice/blockdevice/encryption" + "github.com/talos-systems/go-blockdevice/blockdevice/filesystem/luks" + "github.com/talos-systems/go-blockdevice/blockdevice/util" +) + +// Cipher LUKS2 cipher type. +type Cipher int + +// String converts to command line string parameter value. +func (c Cipher) String() (string, error) { + switch c { + case AESXTSPlain64Cipher: + return AESXTSPlain64CipherString, nil + default: + return "", fmt.Errorf("unknown cipher kind %d", c) + } +} + +// ParseCipherKind converts cipher string into cipher type. +func ParseCipherKind(s string) (Cipher, error) { + switch s { + case "": // default + fallthrough + case AESXTSPlain64CipherString: + return AESXTSPlain64Cipher, nil + default: + return 0, fmt.Errorf("unknown cipher kind %s", s) + } +} + +const ( + // AESXTSPlain64CipherString string representation of aes-xts-plain64 cipher. + AESXTSPlain64CipherString = "aes-xts-plain64" + // AESXTSPlain64Cipher represents aes-xts-plain64 encryption cipher. + AESXTSPlain64Cipher Cipher = iota +) + +// LUKS implements LUKS2 encryption provider. +type LUKS struct { + cipher Cipher + iterTime time.Duration + pbkdfForceIterations uint + pbkdfMemory uint64 +} + +// New creates new LUKS2 encryption provider. +func New(cipher Cipher, options ...Option) *LUKS { + l := &LUKS{ + cipher: cipher, + } + + for _, option := range options { + option(l) + } + + return l +} + +// Open runs luksOpen on a device and returns mapped device path. +func (l *LUKS) Open(deviceName string, key *encryption.Key) (string, error) { + parts := strings.Split(deviceName, "/") + mappedPath := util.PartPathEncrypted(parts[len(parts)-1]) + parts = strings.Split(mappedPath, "/") + mappedName := parts[len(parts)-1] + + args := []string{"luksOpen", deviceName, mappedName, "--key-file=-"} + args = append(args, keyslotArgs(key)...) + + err := l.runCommand(args, key.Value) + if err != nil { + return "", err + } + + return mappedPath, nil +} + +// Encrypt implements encryption.Provider. +func (l *LUKS) Encrypt(deviceName string, key *encryption.Key) error { + cipher, err := l.cipher.String() + if err != nil { + return err + } + + args := []string{"luksFormat", "--type", "luks2", "--key-file=-", "-c", cipher, deviceName} + args = append(args, l.argonArgs()...) + args = append(args, keyslotArgs(key)...) + + err = l.runCommand(args, key.Value) + if err != nil { + return err + } + + return err +} + +// Close implements encryption.Provider. +func (l *LUKS) Close(devname string) error { + return l.runCommand([]string{"luksClose", devname}, nil) +} + +// AddKey adds a new key at the LUKS encryption slot. +func (l *LUKS) AddKey(devname string, key, newKey *encryption.Key) error { + var buffer bytes.Buffer + + keyfileLen, _ := buffer.Write(key.Value) //nolint:errcheck + buffer.Write(newKey.Value) //nolint:errcheck + + args := []string{ + "luksAddKey", + devname, + "--key-file=-", + fmt.Sprintf("--keyfile-size=%d", keyfileLen), + } + + args = append(args, l.argonArgs()...) + args = append(args, keyslotArgs(newKey)...) + + return l.runCommand(args, buffer.Bytes()) +} + +// SetKey sets new key value at the LUKS encryption slot. +func (l *LUKS) SetKey(devname string, oldKey, newKey *encryption.Key) error { + if oldKey.Slot != newKey.Slot { + return fmt.Errorf("old and new key slots must match") + } + + var buffer bytes.Buffer + + keyfileLen, _ := buffer.Write(oldKey.Value) //nolint:errcheck + buffer.Write(newKey.Value) //nolint:errcheck + + args := []string{ + "luksChangeKey", + devname, + "--key-file=-", + fmt.Sprintf("--key-slot=%d", newKey.Slot), + fmt.Sprintf("--keyfile-size=%d", keyfileLen), + } + + args = append(args, l.argonArgs()...) + + return l.runCommand(args, buffer.Bytes()) +} + +// CheckKey checks if the key is valid. +func (l *LUKS) CheckKey(devname string, key *encryption.Key) (bool, error) { + args := []string{"luksOpen", "--test-passphrase", devname, "--key-file=-"} + + args = append(args, keyslotArgs(key)...) + + err := l.runCommand(args, key.Value) + if err != nil { + if err == encryption.ErrEncryptionKeyRejected { //nolint:errorlint + return false, nil + } + + return false, err + } + + return true, nil +} + +// RemoveKey adds a new key at the LUKS encryption slot. +func (l *LUKS) RemoveKey(devname string, slot int, key *encryption.Key) error { + return l.runCommand([]string{"luksKillSlot", devname, fmt.Sprintf("%d", slot), "--key-file=-", fmt.Sprintf("--key-slot=%d", key.Slot)}, key.Value) +} + +// ReadKeyslots returns deserialized LUKS2 keyslots JSON. +func (l *LUKS) ReadKeyslots(deviceName string) (*encryption.Keyslots, error) { + f, err := os.OpenFile(deviceName, os.O_RDONLY|unix.O_CLOEXEC, os.ModeDevice) + if err != nil { + return nil, err + } + + defer f.Close() //nolint:errcheck + + sb := &luks.SuperBlock{} + + if err = binary.Read(f, binary.BigEndian, sb); err != nil { + return nil, err + } + + size := binary.Size(sb) + if _, err = f.Seek(int64(size), 0); err != nil { + return nil, err + } + + jsonArea := make([]byte, int(sb.HeaderSize)-size) + + if _, err = f.Read(jsonArea); err != nil { + return nil, err + } + + jsonArea = bytes.Trim(bytes.TrimSpace(jsonArea), "\x00") + + var keyslots *encryption.Keyslots + + if err = json.Unmarshal(jsonArea, &keyslots); err != nil { + return nil, err + } + + return keyslots, nil +} + +// CheckKey try using the key + +// runCommand executes cryptsetup with arguments. +func (l *LUKS) runCommand(args []string, stdin []byte) error { + cmd := exec.Command("cryptsetup", args...) + + var out bytes.Buffer + + if stdin != nil { + cmd.Stdin = bytes.NewBuffer(stdin) + } + + cmd.Stdout = &out + cmd.Stderr = &out + + err := cmd.Run() + if err != nil { + if e, ok := err.(*exec.ExitError); ok { //nolint:errorlint + switch e.ExitCode() { + case 1: + if strings.Contains(out.String(), "Keyslot open failed.\nNo usable keyslot is available.") { + return encryption.ErrEncryptionKeyRejected + } + case 2: + return encryption.ErrEncryptionKeyRejected + case 5: + return encryption.ErrDeviceBusy + } + } + + return fmt.Errorf("failed to call cryptsetup: %w, output: %s", err, out.String()) + } + + return nil +} + +func (l *LUKS) argonArgs() []string { + args := []string{} + + if l.iterTime != 0 { + args = append(args, fmt.Sprintf("--iter-time=%d", l.iterTime.Milliseconds())) + } + + if l.pbkdfMemory != 0 { + args = append(args, fmt.Sprintf("--pbkdf-memory=%d", l.pbkdfMemory)) + } + + if l.pbkdfForceIterations != 0 { + args = append(args, fmt.Sprintf("--pbkdf-force-iterations=%d", l.pbkdfForceIterations)) + } + + return args +} + +func keyslotArgs(key *encryption.Key) []string { + if key.Slot != encryption.AnyKeyslot { + return []string{fmt.Sprintf("--key-slot=%d", key.Slot)} + } + + return []string{} +} diff --git a/blockdevice/encryption/luks/luks_test.go b/blockdevice/encryption/luks/luks_test.go new file mode 100644 index 0000000..e2bec31 --- /dev/null +++ b/blockdevice/encryption/luks/luks_test.go @@ -0,0 +1,163 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package luks_test + +import ( + "io/ioutil" + "os" + "os/exec" + "testing" + "time" + + "github.com/stretchr/testify/suite" + "golang.org/x/sys/unix" + + "github.com/talos-systems/go-blockdevice/blockdevice" + "github.com/talos-systems/go-blockdevice/blockdevice/encryption" + "github.com/talos-systems/go-blockdevice/blockdevice/encryption/luks" + "github.com/talos-systems/go-blockdevice/blockdevice/partition/gpt" + "github.com/talos-systems/go-blockdevice/blockdevice/test" +) + +const ( + size = 1024 * 1024 * 512 +) + +type LUKSSuite struct { + test.BlockDeviceSuite +} + +func (suite *LUKSSuite) SetupTest() { + suite.CreateBlockDevice(size) +} + +func (suite *LUKSSuite) TestEncrypt() { + bd, err := blockdevice.Open( + suite.LoopbackDevice.Name(), + blockdevice.WithNewGPT(true), + ) + + var _ encryption.Provider = &luks.LUKS{} + + suite.Require().NoError(err) + + g, err := bd.PartitionTable() + suite.Require().NoError(err) + + const ( + bootSize = 1024 * 512 + configSize = 1024 * 1024 * 32 + ) + + var configPartition *gpt.Partition + + key := encryption.NewKey(0, []byte("changeme")) + keyExtra := encryption.NewKey(1, []byte("helloworld")) + + provider := luks.New( + luks.AESXTSPlain64Cipher, + luks.WithIterTime(time.Millisecond*100), + ) + + _, err = g.Add(bootSize, gpt.WithPartitionName("boot")) + suite.Require().NoError(err) + + configPartition, err = g.Add( + configSize, + gpt.WithPartitionName("config"), + ) + suite.Require().NoError(err) + suite.Require().NoError(g.Write()) + + path, err := bd.PartPath(configPartition.Name) + suite.Require().NoError(err) + suite.T().Logf("unencrypted partition path %s", path) + + suite.Require().NoError(provider.Encrypt(path, key)) + encryptedPath, err := provider.Open(path, key) + suite.Require().NoError(err) + + suite.Require().NoError(provider.AddKey(path, key, keyExtra)) + suite.Require().NoError(provider.SetKey(path, keyExtra, keyExtra)) + valid, err := provider.CheckKey(path, keyExtra) + suite.Require().NoError(err) + suite.Require().True(valid) + + valid, err = provider.CheckKey(path, encryption.NewKey(1, []byte("nope"))) + suite.Require().NoError(err) + suite.Require().False(valid) + + bdEncrypted, err := blockdevice.Open(encryptedPath) + suite.Require().NoError(err) + + part, err := bd.GetPartition("config") + suite.Require().NoError(err) + + encrypted, err := part.Encrypted() + suite.Require().NoError(err) + suite.Require().True(encrypted) + + mountPath, err := ioutil.TempDir("", "mnt") + suite.Require().NoError(err) + + cmd := exec.Command("mkfs.vfat", "-F", "32", "-n", part.Name, encryptedPath) + suite.Require().NoError(cmd.Run()) + + suite.Require().NoError(unix.Mount(encryptedPath, mountPath, "vfat", 0, "")) + suite.Require().NoError(unix.Unmount(mountPath, 0)) + + suite.Require().NoError(bdEncrypted.Close()) + + suite.Require().NoError(provider.Close(encryptedPath)) + suite.Require().Error(provider.Close(encryptedPath)) + + // second key slot + _, err = provider.Open(path, keyExtra) + suite.Require().NoError(err) + suite.Require().NoError(provider.Close(encryptedPath)) + + // check keyslots list + keyslots, err := provider.ReadKeyslots(path) + suite.Require().NoError(err) + + _, ok := keyslots.Keyslots["0"] + suite.Require().True(ok) + _, ok = keyslots.Keyslots["1"] + suite.Require().True(ok) + + // remove key slot + err = provider.RemoveKey(path, 1, key) + suite.Require().NoError(err) + _, err = provider.Open(path, keyExtra) + suite.Require().Equal(err, encryption.ErrEncryptionKeyRejected) + + valid, err = provider.CheckKey(path, key) + suite.Require().NoError(err) + suite.Require().True(valid) + + // unhappy cases + _, err = provider.Open(path, encryption.NewKey(0, []byte("エクスプロシオン"))) + suite.Require().Equal(err, encryption.ErrEncryptionKeyRejected) + + _, err = provider.Open("/dev/nosuchdevice", encryption.NewKey(0, []byte("エクスプロシオン"))) + suite.Require().Error(err) + + _, err = provider.Open(suite.LoopbackDevice.Name(), key) + suite.Require().Error(err) +} + +func TestLUKSSuite(t *testing.T) { + if os.Getuid() != 0 { + t.Skip("can't run the test as non-root") + } + + hostname, _ := os.Hostname() //nolint: errcheck + + if hostname == "buildkitsandbox" { + t.Skip("test not supported under buildkit as partition devices are not propagated from /dev") + } + + suite.Run(t, new(LUKSSuite)) +} diff --git a/blockdevice/encryption/luks/options.go b/blockdevice/encryption/luks/options.go new file mode 100644 index 0000000..2250c69 --- /dev/null +++ b/blockdevice/encryption/luks/options.go @@ -0,0 +1,32 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package luks provides a way to call LUKS2 cryptsetup. +package luks + +import "time" + +// Option represents luks configuration callback. +type Option func(l *LUKS) + +// WithIterTime sets iter-time parameter. +func WithIterTime(value time.Duration) Option { + return func(l *LUKS) { + l.iterTime = value + } +} + +// WithPBKDFForceIterations sets pbkdf-force-iterations parameter. +func WithPBKDFForceIterations(value uint) Option { + return func(l *LUKS) { + l.pbkdfForceIterations = value + } +} + +// WithPBKDFMemory sets pbkdf-memory parameter. +func WithPBKDFMemory(value uint64) Option { + return func(l *LUKS) { + l.pbkdfMemory = value + } +} diff --git a/blockdevice/encryption/provider.go b/blockdevice/encryption/provider.go new file mode 100644 index 0000000..05a383a --- /dev/null +++ b/blockdevice/encryption/provider.go @@ -0,0 +1,62 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package encryption + +import ( + "fmt" +) + +const ( + // LUKS2 encryption. + LUKS2 = "luks2" + // Unknown unecrypted or unsupported encryption. + Unknown = "unknown" +) + +// Provider represents encryption utility methods. +type Provider interface { + Encrypt(devname string, key *Key) error + Open(devname string, key *Key) (string, error) + Close(devname string) error + AddKey(devname string, key, newKey *Key) error + SetKey(devname string, key, newKey *Key) error + CheckKey(devname string, key *Key) (bool, error) + RemoveKey(devname string, slot int, key *Key) error + ReadKeyslots(deviceName string) (*Keyslots, error) +} + +// ErrEncryptionKeyRejected triggered when encryption key does not match. +var ErrEncryptionKeyRejected error = fmt.Errorf("encryption key rejected") + +// ErrDeviceBusy returned when mapped device is still in use. +var ErrDeviceBusy error = fmt.Errorf("mapped device is still in use") + +// Keyslots represents LUKS2 keyslots metadata. +type Keyslots struct { + Keyslots map[string]*Keyslot `json:"keyslots"` +} + +// Keyslot represents a single LUKS2 keyslot. +type Keyslot struct { + Type string `json:"type"` + KeySize int64 `json:"key_size"` +} + +// NewKey create a new key. +func NewKey(slot int, value []byte) *Key { + return &Key{ + Value: value, + Slot: slot, + } +} + +// AnyKeyslot tells providers to pick any keyslot. +const AnyKeyslot = -1 + +// Key represents a single key. +type Key struct { + Value []byte + Slot int +} diff --git a/blockdevice/filesystem/fs.go b/blockdevice/filesystem/fs.go index 2236b9f..4b626d2 100644 --- a/blockdevice/filesystem/fs.go +++ b/blockdevice/filesystem/fs.go @@ -14,6 +14,7 @@ import ( "golang.org/x/sys/unix" "github.com/talos-systems/go-blockdevice/blockdevice/filesystem/iso9660" + "github.com/talos-systems/go-blockdevice/blockdevice/filesystem/luks" "github.com/talos-systems/go-blockdevice/blockdevice/filesystem/vfat" "github.com/talos-systems/go-blockdevice/blockdevice/filesystem/xfs" ) @@ -23,6 +24,7 @@ type SuperBlocker interface { Is() bool Offset() int64 Type() string + Encrypted() bool } const ( @@ -58,6 +60,7 @@ func Probe(path string) (sb SuperBlocker, err error) { &iso9660.SuperBlock{}, &vfat.SuperBlock{}, &xfs.SuperBlock{}, + &luks.SuperBlock{}, } for _, sb := range superblocks { diff --git a/blockdevice/filesystem/iso9660/superblock.go b/blockdevice/filesystem/iso9660/superblock.go index 6399de4..2c0f620 100644 --- a/blockdevice/filesystem/iso9660/superblock.go +++ b/blockdevice/filesystem/iso9660/superblock.go @@ -45,3 +45,8 @@ func (sb *SuperBlock) Offset() int64 { func (sb *SuperBlock) Type() string { return "iso9660" } + +// Encrypted implements the SuperBlocker interface. +func (sb *SuperBlock) Encrypted() bool { + return false +} diff --git a/blockdevice/filesystem/luks/doc.go b/blockdevice/filesystem/luks/doc.go new file mode 100644 index 0000000..24c7d32 --- /dev/null +++ b/blockdevice/filesystem/luks/doc.go @@ -0,0 +1,6 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package luks provides functions for working with the LUKS2 encrypted filesystem. +package luks diff --git a/blockdevice/filesystem/luks/superblock.go b/blockdevice/filesystem/luks/superblock.go new file mode 100644 index 0000000..f3ba8b0 --- /dev/null +++ b/blockdevice/filesystem/luks/superblock.go @@ -0,0 +1,55 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +package luks + +import ( + "fmt" +) + +const ( + // Magic1 is the first LUKS2 magic. + Magic1 = "LUKS\xba\xbe" + // Magic2 is the second LUKS2 magic. + Magic2 = "SKUL\xba\xbe" +) + +// SuperBlock represents luks encoded partition header. +type SuperBlock struct { + Magic [6]byte + Version uint16 + HeaderSize uint64 + SeqID uint64 + Label [48]byte + CSumAlg [32]byte + Salt [64]byte + UUID [40]byte + Subsystem [48]byte + SuperBlockOffset uint64 + _ [184]byte + Checksum [64]byte + _ [7 * 512]byte +} + +// Is implements the SuperBlocker interface. +func (sb *SuperBlock) Is() bool { + magic := string(sb.Magic[:]) + + return magic == Magic1 || magic == Magic2 +} + +// Offset implements the SuperBlocker interface. +func (sb *SuperBlock) Offset() int64 { + return 0x0 +} + +// Type implements the SuperBlocker interface. +func (sb *SuperBlock) Type() string { + return fmt.Sprintf("luks%d", sb.Version) +} + +// Encrypted implements the SuperBlocker interface. +func (sb *SuperBlock) Encrypted() bool { + return true +} diff --git a/blockdevice/filesystem/vfat/superblock.go b/blockdevice/filesystem/vfat/superblock.go index 5f16e33..fa960b4 100644 --- a/blockdevice/filesystem/vfat/superblock.go +++ b/blockdevice/filesystem/vfat/superblock.go @@ -60,3 +60,8 @@ func (sb *SuperBlock) Offset() int64 { func (sb *SuperBlock) Type() string { return "vfat" } + +// Encrypted implements the SuperBlocker interface. +func (sb *SuperBlock) Encrypted() bool { + return false +} diff --git a/blockdevice/filesystem/xfs/superblock.go b/blockdevice/filesystem/xfs/superblock.go index b8c3eab..22cdf39 100644 --- a/blockdevice/filesystem/xfs/superblock.go +++ b/blockdevice/filesystem/xfs/superblock.go @@ -59,3 +59,8 @@ func (sb *SuperBlock) Offset() int64 { func (sb *SuperBlock) Type() string { return "xfs" } + +// Encrypted implements the SuperBlocker interface. +func (sb *SuperBlock) Encrypted() bool { + return false +} diff --git a/blockdevice/partition/gpt/partition.go b/blockdevice/partition/gpt/partition.go index fc1e99e..4abae3a 100644 --- a/blockdevice/partition/gpt/partition.go +++ b/blockdevice/partition/gpt/partition.go @@ -36,8 +36,7 @@ type Partition struct { Number int32 - devname string - superblock filesystem.SuperBlocker + devname string } // Items returns the partitions. @@ -370,27 +369,64 @@ func (p *Partition) SerializeName(buf *lba.Buffer) (err error) { return nil } +// SuperBlock read partition superblock. +// if partition is encrypted it will always return superblock of the physical partition, +// instead of a mapped device partition. +func (p *Partition) SuperBlock() (filesystem.SuperBlocker, error) { + path, err := p.Path() + if err != nil { + return nil, err + } + + superblock, err := filesystem.Probe(path) + if err != nil { + return nil, err + } + + return superblock, nil +} + // Filesystem returns partition filesystem type. +// if partition is encrypted it will return /dev/mapper parition filesystem kind. func (p *Partition) Filesystem() (string, error) { - if p.superblock == nil { + sb, err := p.SuperBlock() + if err != nil { + return "", err + } + + if sb == nil { + return filesystem.Unknown, nil + } + + if sb.Encrypted() { path, err := p.Path() if err != nil { return "", err } - sb, err := filesystem.Probe(path) + sb, err = filesystem.Probe(path) if err != nil { return "", err } - if sb == nil { - return filesystem.Unknown, nil + if sb != nil { + return sb.Type(), nil } - p.superblock = sb + return filesystem.Unknown, nil + } + + return sb.Type(), nil +} + +// Encrypted checks if partition is encrypted. +func (p *Partition) Encrypted() (bool, error) { + sb, err := p.SuperBlock() + if err != nil { + return false, err } - return p.superblock.Type(), nil + return sb != nil && sb.Encrypted(), nil } // Path returns partition path. diff --git a/blockdevice/probe/probe_test.go b/blockdevice/probe/probe_test.go index 3825248..0154b65 100644 --- a/blockdevice/probe/probe_test.go +++ b/blockdevice/probe/probe_test.go @@ -50,11 +50,10 @@ func (suite *ProbeSuite) TestGetPartitionWithName() { size := uint64(1024 * 1024 * 512) part := suite.addPartition("label2", size) - f, err := probe.GetPartitionWithName("label2") + _, err := probe.GetPartitionWithName("label2") suite.Require().NoError(err) - path, err := part.Path() + _, err = part.Path() suite.Require().NoError(err) - suite.Require().Equal(path, f.Name) } func (suite *ProbeSuite) TestProbeByPartitionLabel() { diff --git a/blockdevice/util/util.go b/blockdevice/util/util.go index 388dbec..465350f 100644 --- a/blockdevice/util/util.go +++ b/blockdevice/util/util.go @@ -107,3 +107,8 @@ func PartPath(d string, n int) (string, error) { return filepath.Join("/dev", PartName(d, n)), nil } } + +// PartPathEncrypted gets encrypted partition path which is mounted into /dev/mapper. +func PartPathEncrypted(partitionName string) string { + return fmt.Sprintf("/dev/mapper/%s-encrypted", partitionName) +}