Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

use PBKDF2 for the LUKS2 keyslots #286

Merged
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
9 changes: 0 additions & 9 deletions argon2.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ import (
"golang.org/x/xerrors"

"github.com/snapcore/secboot/internal/argon2"
"github.com/snapcore/secboot/internal/luks2"
)

var (
Expand Down Expand Up @@ -60,14 +59,6 @@ type KDFOptions struct {
Parallel int
}

func (o *KDFOptions) luksOpts() luks2.KDFOptions {
return luks2.KDFOptions{
TargetDuration: o.TargetDuration,
MemoryKiB: o.MemoryKiB,
ForceIterations: o.ForceIterations,
Parallel: o.Parallel}
}

func (o *KDFOptions) deriveCostParams(keyLen int, kdf KDF) (*KDFCostParams, error) {
switch {
case int64(o.ForceIterations) > math.MaxUint32:
Expand Down
92 changes: 51 additions & 41 deletions crypt.go
Original file line number Diff line number Diff line change
Expand Up @@ -548,18 +548,13 @@ type InitializeLUKS2ContainerOptions struct {
// the container to be initialized with the default metadata area size.
// If set to a non zero value, it must be a power of 2 between 16KiB
// and 4MiB.
MetadataKiBSize int
MetadataKiBSize uint32

// KeyslotsAreaKiBSize sets the size of the binary keyslot area in KiB.
// Setting this to zero causes the container to be initialized with
// the default keyslots area size. If set to a non-zero value, the
// value must be a multiple of 4KiB up to a maximum of 128MiB.
KeyslotsAreaKiBSize int

// KDFOptions sets the KDF options for the initial keyslot. If this
// is nil then the default settings defined by this package are used
// (4 iterations and a memory cost of 32KiB).
KDFOptions *KDFOptions
KeyslotsAreaKiBSize uint32

// InitialKeyslotName sets the name that will be used to identify
// the initial keyslot. If this is empty, then the name will be
Expand All @@ -574,8 +569,25 @@ func (o *InitializeLUKS2ContainerOptions) formatOpts() *luks2.FormatOptions {
return &luks2.FormatOptions{
MetadataKiBSize: o.MetadataKiBSize,
KeyslotsAreaKiBSize: o.KeyslotsAreaKiBSize,
KDFOptions: o.KDFOptions.luksOpts(),
InlineCryptoEngine: o.InlineCryptoEngine}

// Use a minimal KDF - this is the minimum recommended by SP800-132 and the minimum
// supported by cryptsetup. We have a high entropy key rather than a low-entropy
// passphrase - the input key has the same entropy as the derived key, so there is
// no security benefit to the KDF here but it does slow down unlocking. There currently
// isn't a way to disable it, but it would be disabled if there were. If an adversary is
// going to attempt to brute force unlocking, they could just target other keys with the
// same or lower entropy, such as:
// - the derived key which has the same entropy, by decrypting the keyslot and testing it
// against the stored digest.
// - for the TPM case, the storage key's seed which is 16 bytes, by computing the sealed
// object's HMAC and testing it against the stored one.
KDFOptions: luks2.KDFOptions{
Type: luks2.KDFTypePBKDF2,
ForceIterations: 1000,
Hash: luks2.HashSHA256,
},

InlineCryptoEngine: o.InlineCryptoEngine}
}

// InitializeLUKS2Container will initialize the partition at the specified devicePath
Expand Down Expand Up @@ -614,18 +626,6 @@ func InitializeLUKS2Container(devicePath, label string, key DiskUnlockKey, optio
if options == nil {
var defaultOptions InitializeLUKS2ContainerOptions
options = &defaultOptions
} else {
// copy options to avoid modification of the supplied struct
options = &InitializeLUKS2ContainerOptions{
MetadataKiBSize: options.MetadataKiBSize,
KeyslotsAreaKiBSize: options.KeyslotsAreaKiBSize,
KDFOptions: options.KDFOptions,
InitialKeyslotName: options.InitialKeyslotName,
InlineCryptoEngine: options.InlineCryptoEngine}
}

if options.KDFOptions == nil {
options.KDFOptions = &KDFOptions{MemoryKiB: 32, ForceIterations: 4}
}

initialKeyslotName := options.InitialKeyslotName
Expand Down Expand Up @@ -658,7 +658,7 @@ func removeOrphanedTokens(devicePath string, view *luksview.View) {
}
}

func addLUKS2ContainerKey(devicePath, keyslotName string, existingKey, newKey DiskUnlockKey, options *KDFOptions,
func addLUKS2ContainerKey(devicePath, keyslotName string, existingKey, newKey DiskUnlockKey, options *luks2.KDFOptions,
newToken func(base *luksview.TokenBase) luks2.Token, priority luks2.SlotPriority) error {
view, err := newLUKSView(devicePath, luks2.LockModeBlocking)
if err != nil {
Expand All @@ -679,7 +679,7 @@ func addLUKS2ContainerKey(devicePath, keyslotName string, existingKey, newKey Di
freeSlot++
}

if err := luks2AddKey(devicePath, existingKey, newKey, &luks2.AddKeyOptions{KDFOptions: options.luksOpts(), Slot: freeSlot}); err != nil {
if err := luks2AddKey(devicePath, existingKey, newKey, &luks2.AddKeyOptions{KDFOptions: *options, Slot: freeSlot}); err != nil {
return xerrors.Errorf("cannot add key: %w", err)
}

Expand Down Expand Up @@ -765,7 +765,7 @@ func listLUKS2ContainerKeyNames(devicePath string, tokenType luks2.TokenType) ([
// The new key should be protected by some platform-specific mechanism in
// order to create a KeyData object. The KeyData object can be saved to the
// keyslot using LUKS2KeyDataWriter.
func AddLUKS2ContainerUnlockKey(devicePath, keyslotName string, existingKey, newKey DiskUnlockKey, options *KDFOptions) error {
func AddLUKS2ContainerUnlockKey(devicePath, keyslotName string, existingKey, newKey DiskUnlockKey) error {
if len(newKey) < 32 {
return fmt.Errorf("expected a key length of at least 256-bits (got %d)", len(newKey)*8)
}
Expand All @@ -774,18 +774,23 @@ func AddLUKS2ContainerUnlockKey(devicePath, keyslotName string, existingKey, new
keyslotName = defaultKeyslotName
}

// Use a reduced cost for the KDF. This is done because we have a high entropy key rather
// than a low entropy passphrase. Setting a higher cost provides no security benefit but
// does slow down unlocking. If an adversary is going to attempt to brute force this key,
// then they could instead turn their attention to one of the other keys involved in the
// protection of this key, some of which can be verified without running a KDF. For
// example, with a TPM sealed object, you can verify the parent storage key's seed by
// computing the key object's HMAC key and verifying the integrity value on the outer wrapper.
if options == nil {
options = &KDFOptions{MemoryKiB: 32, ForceIterations: 4}
}

return addLUKS2ContainerKey(devicePath, keyslotName, existingKey, newKey, options, func(base *luksview.TokenBase) luks2.Token {
// Use a minimal KDF - this is the minimum recommended by SP800-132 and the minimum
// supported by cryptsetup. We have a high entropy key rather than a low-entropy
// passphrase - the input key has the same entropy as the derived key, so there is
// no security benefit to the KDF here but it does slow down unlocking. There currently
// isn't a way to disable it, but it would be disabled if there were. If an adversary is
// going to attempt to brute force unlocking, they could just target other keys with the
// same or lower entropy, such as:
// - the derived key which has the same entropy, by decrypting the keyslot and testing it
// against the stored digest.
// - for the TPM case, the storage key's seed which is 16 bytes, by computing the sealed
// object's HMAC and testing it against the stored one.
options := luks2.KDFOptions{
Type: luks2.KDFTypePBKDF2,
ForceIterations: 1000,
Hash: luks2.HashSHA256,
}
return addLUKS2ContainerKey(devicePath, keyslotName, existingKey, newKey, &options, func(base *luksview.TokenBase) luks2.Token {
return &luksview.KeyDataToken{TokenBase: *base}
}, luks2.SlotPriorityHigh)
}
Expand Down Expand Up @@ -813,16 +818,21 @@ func ListLUKS2ContainerUnlockKeyNames(devicePath string) ([]string, error) {
// with RenameLUKS2ContainerKey.
//
// In order to perform this action, an existing key must be supplied.
func AddLUKS2ContainerRecoveryKey(devicePath, keyslotName string, existingKey DiskUnlockKey, recoveryKey RecoveryKey, options *KDFOptions) error {
func AddLUKS2ContainerRecoveryKey(devicePath, keyslotName string, existingKey DiskUnlockKey, recoveryKey RecoveryKey) error {
if keyslotName == "" {
keyslotName = defaultRecoveryKeyslotName
}

if options == nil {
options = &KDFOptions{}
// Use PBKDF2 with the current OWASP recommendations - 600000 iterations
// and SHA256. The recovery key has an entropy of 16 bytes which is strong
// and this is overkill really - this could be knocked down to minimal settings
// if we have a 32 byte recovery key.
options := luks2.KDFOptions{
Type: luks2.KDFTypePBKDF2,
ForceIterations: 600000,
Hash: luks2.HashSHA256,
}

return addLUKS2ContainerKey(devicePath, keyslotName, existingKey, recoveryKey[:], options, func(base *luksview.TokenBase) luks2.Token {
return addLUKS2ContainerKey(devicePath, keyslotName, existingKey, recoveryKey[:], &options, func(base *luksview.TokenBase) luks2.Token {
return &luksview.RecoveryToken{TokenBase: *base}
}, luks2.SlotPriorityNormal)
}
Expand Down
Loading
Loading