Skip to content

Commit

Permalink
Merge pull request #291 from chrisccoulson/prepare-for-pbkdf2-for-pas…
Browse files Browse the repository at this point in the history
…sphrase-support

argon2: refactor API to prepare for PBKDF2 support for passphrases.

This renames the existing KDFOptions, KDFCostParams and KDF types to
Argon2Options, Argon2CostParams and Argon2KDF in preparation for
the introduction of PBKDF2 support for passphrases in another PR, which
will use a new options implementation. Both options implementations will
implement a new interface (KDFOptions).

As PBKDF2 won't be abstracted like Argon2 is because it doesn't use lots
of memory, also remove the kdf argument from functions that take them.
Instead, have a process wide Argon2KDF implementation, set by calling
SetArgon2KDF.
  • Loading branch information
chrisccoulson authored Mar 15, 2024
2 parents 75a8293 + 6985b8f commit a33cfb0
Show file tree
Hide file tree
Showing 13 changed files with 251 additions and 270 deletions.
121 changes: 83 additions & 38 deletions argon2.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ package secboot

import (
"errors"
"math"
"runtime"
"sync"
"time"

"golang.org/x/xerrors"
Expand All @@ -31,16 +31,45 @@ import (
)

var (
argon2Mu sync.Mutex
argon2Impl Argon2KDF = nullArgon2KDFImpl{}

runtimeNumCPU = runtime.NumCPU
)

// KDFOptions specifies parameters for the Argon2 KDF used by cryptsetup
// SetArgon2KDF sets the KDF implementation for Argon2. The default here is
// the null implementation which returns an error, so this will need to be
// configured explicitly in order to use Argon2.
//
// Passing nil will configure the null implementation as well.
//
// This returns the currently set implementation.
func SetArgon2KDF(kdf Argon2KDF) Argon2KDF {
argon2Mu.Lock()
defer argon2Mu.Unlock()

orig := argon2Impl
if kdf == nil {
argon2Impl = nullArgon2KDFImpl{}
} else {
argon2Impl = kdf
}
return orig
}

func argon2KDF() Argon2KDF {
argon2Mu.Lock()
defer argon2Mu.Unlock()
return argon2Impl
}

// Argon2Options specifies parameters for the Argon2 KDF used by cryptsetup
// and for passphrase support.
type KDFOptions struct {
type Argon2Options struct {
// MemoryKiB specifies the maximum memory cost in KiB when ForceIterations
// is zero. If ForceIterations is not zero, then this is used as the
// memory cost.
MemoryKiB int
MemoryKiB uint32

// TargetDuration specifies the target duration for the KDF which
// is used to benchmark the time and memory cost parameters. If it
Expand All @@ -51,39 +80,31 @@ type KDFOptions struct {
// ForceIterations can be used to turn off KDF benchmarking by
// setting the time cost directly. If this is zero then the cost
// parameters are benchmarked based on the value of TargetDuration.
ForceIterations int
ForceIterations uint32

// Parallel sets the maximum number of parallel threads for the
// KDF (up to 4). This will be adjusted downwards based on the
// actual number of CPUs.
Parallel int
Parallel uint8
}

func (o *KDFOptions) deriveCostParams(keyLen int, kdf KDF) (*KDFCostParams, error) {
func (o *Argon2Options) deriveCostParams(keyLen int) (*Argon2CostParams, error) {
switch {
case int64(o.ForceIterations) > math.MaxUint32:
return nil, errors.New("ForceIterations too large")
case int64(o.MemoryKiB) > math.MaxUint32:
return nil, errors.New("MemoryKiB too large")
case o.Parallel > math.MaxUint8:
return nil, errors.New("Parallel too large")
case o.ForceIterations < 0:
return nil, errors.New("ForceIterations can't be negative")
case o.ForceIterations > 0:
threads := runtimeNumCPU()
if threads > 4 {
threads = 4
}
params := &KDFCostParams{
Time: uint32(o.ForceIterations),
params := &Argon2CostParams{
Time: o.ForceIterations,
MemoryKiB: 1 * 1024 * 1024,
Threads: uint8(threads)}

if o.MemoryKiB != 0 {
params.MemoryKiB = uint32(o.MemoryKiB)
params.MemoryKiB = o.MemoryKiB
}
if o.Parallel != 0 {
params.Threads = uint8(o.Parallel)
params.Threads = o.Parallel
if o.Parallel > 4 {
params.Threads = 4
}
Expand All @@ -96,20 +117,20 @@ func (o *KDFOptions) deriveCostParams(keyLen int, kdf KDF) (*KDFCostParams, erro
TargetDuration: 2 * time.Second}

if o.MemoryKiB != 0 {
benchmarkParams.MaxMemoryCostKiB = uint32(o.MemoryKiB)
benchmarkParams.MaxMemoryCostKiB = o.MemoryKiB
}
if o.TargetDuration != 0 {
benchmarkParams.TargetDuration = o.TargetDuration
}
if o.Parallel != 0 {
benchmarkParams.Threads = uint8(o.Parallel)
benchmarkParams.Threads = o.Parallel
if o.Parallel > 4 {
benchmarkParams.Threads = 4
}
}

params, err := argon2.Benchmark(benchmarkParams, func(params *argon2.CostParams) (time.Duration, error) {
return kdf.Time(&KDFCostParams{
return argon2KDF().Time(&Argon2CostParams{
Time: params.Time,
MemoryKiB: params.MemoryKiB,
Threads: params.Threads})
Expand All @@ -118,15 +139,15 @@ func (o *KDFOptions) deriveCostParams(keyLen int, kdf KDF) (*KDFCostParams, erro
return nil, xerrors.Errorf("cannot benchmark KDF: %w", err)
}

return &KDFCostParams{
return &Argon2CostParams{
Time: params.Time,
MemoryKiB: params.MemoryKiB,
Threads: params.Threads}, nil
}
}

// KDFCostParams defines the cost parameters for key derivation using Argon2.
type KDFCostParams struct {
// Argon2CostParams defines the cost parameters for key derivation using Argon2.
type Argon2CostParams struct {
// Time corresponds to the number of iterations of the algorithm
// that the key derivation will use.
Time uint32
Expand All @@ -140,41 +161,65 @@ type KDFCostParams struct {
Threads uint8
}

func (p *KDFCostParams) internalParams() *argon2.CostParams {
func (p *Argon2CostParams) internalParams() *argon2.CostParams {
return &argon2.CostParams{
Time: p.Time,
MemoryKiB: p.MemoryKiB,
Threads: p.Threads}
}

// KDF is an interface to abstract use of the Argon2 KDF to make it possible
// Argon2KDF is an interface to abstract use of the Argon2 KDF to make it possible
// to delegate execution to a short-lived utility process where required.
type KDF interface {
type Argon2KDF interface {
// Derive derives a key of the specified length in bytes, from the supplied
// passphrase and salt and using the supplied cost parameters.
Derive(passphrase string, salt []byte, params *KDFCostParams, keyLen uint32) ([]byte, error)
Derive(passphrase string, salt []byte, params *Argon2CostParams, keyLen uint32) ([]byte, error)

// Time measures the amount of time the KDF takes to execute with the
// specified cost parameters.
Time(params *KDFCostParams) (time.Duration, error)
Time(params *Argon2CostParams) (time.Duration, error)
}

type argon2iKDFImpl struct{}
type inProcessArgon2KDFImpl struct{}

func (_ inProcessArgon2KDFImpl) Derive(passphrase string, salt []byte, params *Argon2CostParams, keyLen uint32) ([]byte, error) {
switch {
case params == nil:
return nil, errors.New("nil params")
case params.Time == 0:
return nil, errors.New("invalid time cost")
case params.Threads == 0:
return nil, errors.New("invalid number of threads")
}

func (_ argon2iKDFImpl) Derive(passphrase string, salt []byte, params *KDFCostParams, keyLen uint32) ([]byte, error) {
return argon2.Key(passphrase, salt, params.internalParams(), keyLen), nil
}

func (_ argon2iKDFImpl) Time(params *KDFCostParams) (time.Duration, error) {
func (_ inProcessArgon2KDFImpl) Time(params *Argon2CostParams) (time.Duration, error) {
switch {
case params == nil:
return 0, errors.New("nil params")
case params.Time == 0:
return 0, errors.New("invalid time cost")
case params.Threads == 0:
return 0, errors.New("invalid number of threads")
}

return argon2.KeyDuration(params.internalParams()), nil
}

var argon2iKDF = argon2iKDFImpl{}

// Argon2iKDF returns the in-process Argon2i implementation of KDF. This
// InProcessArgon2KDF is the in-process implementation of the Argon2 KDF. This
// shouldn't be used in long-lived system processes - these processes should
// instead provide their own KDF implementation which delegates to a short-lived
// utility process which will use the in-process implementation.
func Argon2iKDF() KDF {
return argon2iKDF
var InProcessArgon2KDF = inProcessArgon2KDFImpl{}

type nullArgon2KDFImpl struct{}

func (_ nullArgon2KDFImpl) Derive(passphrase string, salt []byte, params *Argon2CostParams, keyLen uint32) ([]byte, error) {
return nil, errors.New("no argon2 KDF: please call secboot.SetArgon2KDF")
}

func (_ nullArgon2KDFImpl) Time(params *Argon2CostParams) (time.Duration, error) {
return 0, errors.New("no argon2 KDF: please call secboot.SetArgon2KDF")
}
Loading

0 comments on commit a33cfb0

Please sign in to comment.