From 017287bc5671ab0bbcc9e3dd8f5c75de5458b09d Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Tue, 3 Sep 2024 21:54:37 +0100 Subject: [PATCH] argon2: Add helpers for running the KDF remotely As Argon2 is memory intensive, it's not suitable for multiple invocations in long-lived garbage collected processes. For this reason, Argon2 is abstracted with an interface (Argon2KDF), of which the application sets a global version of this which is intended to proxy KDF requests to a short-lived remote process which uses the real InProcessArgon2KDF. This adds some functionality to facilitate this. First of all, InProcessArgon2KDF is no longer a variable - it's a function. By default, it's methods return an error unless the application code has called SetIsArgon2RemoteProcess, which unlocks the real in-process KDF. Then there are JSON serializable types "Argon2RemoteInput" and "Argon2RemoteOutput". The input can be fed directly to RunArgon2RequestInRemoteProcess on the remote side, but this is a fairly low-level API. There is a higher level API - NewRemoteArgon2KDF, for use in the application process, and which returns an implementation of Argon2KDF which proxies requests to a short-lived remote helper process. The caller supplied a function to construct an appropriate exec.Cmd instance for this. This function is configured so that the remote process recieves a request on stdin and it expects a response on stdout. The remote process passes both os.Stdin and os.Stdout to WaitAndRunArgon2RequestInRemoteProcess, although it doesn't hardcode these descriptors for implementations that want to construct their own transport that doesn't rely on stdin and stdout. Once a remote process has completed a request, it should exit cleanly. Neither RunArgon2RequestInRemoteProcess or WaitAndRunArgon2RequestInRemoteProcess support being called more than once in the same process. The code in cmd/run_argon2 provides an example remote process, although this is mainly useful for unit testing (where it is currently used). It is envisaged that the remote process will be a special mode of snapd and snap-bootstrap in order to avoid adding an additional new go binary just for this. --- .gitignore | 1 + argon2.go | 38 -- argon2_remote_support.go | 460 ++++++++++++++++++++++++ argon2_remote_support_test.go | 643 ++++++++++++++++++++++++++++++++++ argon2_test.go | 173 --------- cmd/run_argon2/main.go | 50 +++ export_test.go | 7 + 7 files changed, 1161 insertions(+), 211 deletions(-) create mode 100644 argon2_remote_support.go create mode 100644 argon2_remote_support_test.go create mode 100644 cmd/run_argon2/main.go diff --git a/.gitignore b/.gitignore index 5a46d357..eb54f6c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +run_argon2 vendor/*/ diff --git a/argon2.go b/argon2.go index 2209816c..6d5aa507 100644 --- a/argon2.go +++ b/argon2.go @@ -225,44 +225,6 @@ type Argon2KDF interface { Time(mode Argon2Mode, params *Argon2CostParams) (time.Duration, error) } -type inProcessArgon2KDFImpl struct{} - -func (_ inProcessArgon2KDFImpl) Derive(passphrase string, salt []byte, mode Argon2Mode, params *Argon2CostParams, keyLen uint32) ([]byte, error) { - switch { - case mode != Argon2i && mode != Argon2id: - return nil, errors.New("invalid mode") - 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") - } - - return argon2.Key(passphrase, salt, argon2.Mode(mode), params.internalParams(), keyLen), nil -} - -func (_ inProcessArgon2KDFImpl) Time(mode Argon2Mode, params *Argon2CostParams) (time.Duration, error) { - switch { - case mode != Argon2i && mode != Argon2id: - return 0, errors.New("invalid mode") - 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(argon2.Mode(mode), params.internalParams()), nil -} - -// 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. -var InProcessArgon2KDF = inProcessArgon2KDFImpl{} - type nullArgon2KDFImpl struct{} func (_ nullArgon2KDFImpl) Derive(passphrase string, salt []byte, mode Argon2Mode, params *Argon2CostParams, keyLen uint32) ([]byte, error) { diff --git a/argon2_remote_support.go b/argon2_remote_support.go new file mode 100644 index 00000000..d75cadbe --- /dev/null +++ b/argon2_remote_support.go @@ -0,0 +1,460 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2021-2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package secboot + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "os/exec" + "sync/atomic" + "time" + + "github.com/snapcore/secboot/internal/argon2" +) + +type nullInProcessArgon2KDFImpl struct{} + +func (_ nullInProcessArgon2KDFImpl) Derive(passphrase string, salt []byte, mode Argon2Mode, params *Argon2CostParams, keyLen uint32) ([]byte, error) { + return nil, errors.New("no argon2 KDF: please call secboot.SetIsArgon2RemoteProcess if the intention is to run Argon2 directly in this process") +} + +func (_ nullInProcessArgon2KDFImpl) Time(mode Argon2Mode, params *Argon2CostParams) (time.Duration, error) { + return 0, errors.New("no argon2 KDF: please call secboot.SetIsArgon2RemoteProcess if the intention is to run Argon2 directly in this process") +} + +type inProcessArgon2KDFImpl struct{} + +func (_ inProcessArgon2KDFImpl) Derive(passphrase string, salt []byte, mode Argon2Mode, params *Argon2CostParams, keyLen uint32) ([]byte, error) { + switch { + case mode != Argon2i && mode != Argon2id: + return nil, errors.New("invalid mode") + 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") + } + + return argon2.Key(passphrase, salt, argon2.Mode(mode), params.internalParams(), keyLen), nil +} + +func (_ inProcessArgon2KDFImpl) Time(mode Argon2Mode, params *Argon2CostParams) (time.Duration, error) { + switch { + case mode != Argon2i && mode != Argon2id: + return 0, errors.New("invalid mode") + 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(argon2.Mode(mode), params.internalParams()), nil +} + +// InProcessArgon2KDF returns 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 +// [Argon2KDF] implementation which proxies requests to a short-lived remote process which will +// use this in-process implementation once and then exit. This approach avoids memory exhaustion +// and the need to run a full GC mark and sweep ([runtime.GC]) between invocations, which has +// a significant time penalty. Argon2 isn't really compativle with garbage collected runtimes. +// +// There are plenty of helpers in this package to facilitate this, such as JSON serializable +// types ([Argon2RemoteInput] and [Argon2RemoteOutput]) and a function ([RunArgon2KDFInRemoteProcess]) +// that can process these types to run the KDF in-process using a request from a parent process. +// +// There are higher-level helpers too, such as an [Argon2KDF] implementation that can be created +// in the parent process and which creates new remote processes to send each command to (see +// [NewRemoteArgon2KDF]). The remote process is then able to process an incoming request by passing +// [os.Stdin] and [os.Stdout] directly to [WaitAndRunArgon2RequestInRemoteProcess]. +// +// It is indended that the remote process is a special mode of argv[0] (ie, snapd or snap-bootstrap) +// in order to avoid the bloat of adding additional go binaries. +// +// A process must call [SetIsArgon2RemoteProcess] before this returns anything other than a null +// implementation of the KDF. +// +// Note that whilst [WaitAndRunArgon2RequestInRemoteProcess] and [RunArgon2KDFInRemoteProcess] +// contain protections that only allow a single invocation of this KDF in the lifetime of a process, +// direct access via this API doesn't provde the same protections. +func InProcessArgon2KDF() Argon2KDF { + if atomic.LoadUint32(&argon2RemoteProcessStatus) > notArgon2RemoteProcess { + return inProcessArgon2KDFImpl{} + } + return nullInProcessArgon2KDFImpl{} +} + +// Argon2RemoteCommand represents the command to run. +type Argon2RemoteCommand string + +const ( + // Argon2RemoteCommandDerive requests to derive a key from a passphrase + Argon2RemoteCommandDerive Argon2RemoteCommand = "derive" + + // Argon2RemoteCommandTime requests the duration that the KDF took to + // execute. This excludes things like process startup. + Argon2RemoteCommandTime Argon2RemoteCommand = "time" +) + +// Argon2RemoteInput is an input request for an argon2 operation in +// a remote process. +type Argon2RemoteInput struct { + Command Argon2RemoteCommand `json:"command"` // The command to run + Passphrase string `json:"passphrase,omitempty"` // If the command is "derive, the passphrase + Salt []byte `json:"salt,omitempty"` // If the command is "derive", the salt + Keylen uint32 `json:"keylen,omitempty"` // If the command is "derive", the key length in bytes + Mode Argon2Mode `json:"mode"` // The Argon2 mode + Time uint32 `json:"time"` // The time cost + MemoryKiB uint32 `json:"memory"` // The memory cost in KiB + Threads uint8 `json:"threads"` // The number of threads to use +} + +// Argon2RemoteErrorType describes the type of error produced by [RunArgon2RequestInRemoteProcess]. +type Argon2RemoteErrorType string + +const ( + // Argon2RemoteErrorInvalidCommand means that an invalid command was supplied. + Argon2RemoteErrorInvalidCommand Argon2RemoteErrorType = "invalid-command" + + // Argon2RemoteErrorInvalidMode means that an invalid mode was supplied. + Argon2RemoteErrorInvalidMode Argon2RemoteErrorType = "invalid-mode" + + // Argon2RemoteErrorInvalidTimeCost means that an invalid time cost was supplied. + Argon2RemoteErrorInvalidTimeCost Argon2RemoteErrorType = "invalid-time-cost" + + // Argon2RemoteErrorInvalidThreads means that an invalid number of threads was supplied. + Argon2RemoteErrorInvalidThreads Argon2RemoteErrorType = "invalid-threads" + + // Argon2RemoteErrorConsumedProcess means that this process has already performed one + // execution of the KDF, and a new process should replace it. + Argon2RemoteErrorConsumedProcess Argon2RemoteErrorType = "consumed-process" + + // Argon2RemoteErrorProcessNotConfigured means that nothing has called SetIsArgon2RemoteProcess. + Argon2RemoteErrorProcessNotConfigured Argon2RemoteErrorType = "process-not-configured" + + // Argon2RemoteErrorUnexpected means that an unexpected error occurred. + Argon2RemoteErrorUnexpected Argon2RemoteErrorType = "unexpected-error" + + // Argon2RemoteErrorUnexpectedInput means that there was an error with + // the supplied error not covered by one of the more specific error types.. + Argon2RemoteErrorUnexpectedInput Argon2RemoteErrorType = "unexpected-input" +) + +// Argon2RemoteOutput is the response to a request for an argon2 +// operation in a remote process. +type Argon2RemoteOutput struct { + Command Argon2RemoteCommand `json:"command"` // The input command + Key []byte `json:"key,omitempty"` // The derived key, if the input command was "derive" + Duration time.Duration `json:"duration,omitempty"` // The duration, if the input command was "duration" + ErrorType Argon2RemoteErrorType `json:"error-type,omitempty"` // The error type, if an error occurred + ErrorString string `json:"error-string,omitempty"` // The error string, if an error occurred +} + +// Argon2RemoteError is returned from the [Argon2] implentation created be +// [NewRemoteArgon2KDF] when the received response indicates that an error +// ocurred. +type Argon2RemoteError struct { + ErrorType Argon2RemoteErrorType + ErrorString string +} + +func (e *Argon2RemoteError) Error() string { + str := new(bytes.Buffer) + fmt.Fprintf(str, "cannot process KDF request: %v", e.ErrorType) + if e.ErrorString != "" { + fmt.Fprintf(str, " (%s)", e.ErrorString) + } + return str.String() +} + +func (o *Argon2RemoteOutput) Err() error { + if o.ErrorType == "" { + return nil + } + return &Argon2RemoteError{ + ErrorType: o.ErrorType, + ErrorString: o.ErrorString, + } +} + +const ( + notArgon2RemoteProcess uint32 = 0 + readyArgon2RemoteProcess uint32 = 1 + expiredArgon2RemoteProcess uint32 = 2 +) + +var ( + argon2RemoteProcessStatus uint32 = notArgon2RemoteProcess +) + +// SetIsArgon2RemoteProcess marks this process as being a remote processs capable of running +// Argon2 in process. After calling this, [InProcessArgon2KDF] will return a real implementation +// that runs in process. +func SetIsArgon2RemoteProcess() { + if !atomic.CompareAndSwapUint32(&argon2RemoteProcessStatus, notArgon2RemoteProcess, readyArgon2RemoteProcess) { + panic("cannot call SetIsArgon2RemoteProcess more than once") + } +} + +// RunArgon2RequestInRemoteProcess runs the specified argon2 request, and returns a response. This +// function can only be called once in a process. Subsequent calls in the same process will result +// in an error response being returned. +func RunArgon2RequestInRemoteProcess(input *Argon2RemoteInput) *Argon2RemoteOutput { + if !atomic.CompareAndSwapUint32(&argon2RemoteProcessStatus, readyArgon2RemoteProcess, expiredArgon2RemoteProcess) { + switch atomic.LoadUint32(&argon2RemoteProcessStatus) { + case expiredArgon2RemoteProcess: + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorConsumedProcess, + ErrorString: "cannot run more than once in the same process", + } + default: + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorProcessNotConfigured, + ErrorString: "cannot run in a process that isn't configured as an Argon2 remote process", + } + } + } + + switch input.Mode { + case Argon2id, Argon2i: + // ok + default: + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorInvalidMode, + ErrorString: fmt.Sprintf("invalid mode: %q", string(input.Mode)), + } + } + + costParams := &Argon2CostParams{ + Time: input.Time, + MemoryKiB: input.MemoryKiB, + Threads: input.Threads, + } + if costParams.Time == 0 { + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorInvalidTimeCost, + ErrorString: "invalid time cost: cannot be zero", + } + } + if costParams.Threads == 0 { + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorInvalidThreads, + ErrorString: "invalid threads: cannot be zero", + } + } + + switch input.Command { + case Argon2RemoteCommandDerive: + key, err := InProcessArgon2KDF().Derive(input.Passphrase, input.Salt, input.Mode, costParams, input.Keylen) + if err != nil { + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorUnexpected, + ErrorString: fmt.Sprintf("cannot run derive command: %v", err), + } + + } + return &Argon2RemoteOutput{ + Command: input.Command, + Key: key, + } + case Argon2RemoteCommandTime: + if len(input.Passphrase) > 0 { + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorUnexpectedInput, + ErrorString: "cannot supply passphrase for \"time\" command", + } + } + if len(input.Salt) > 0 { + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorUnexpectedInput, + ErrorString: "cannot supply salt for \"time\" command", + } + } + if input.Keylen > 0 { + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorUnexpectedInput, + ErrorString: "cannot supply keylen for \"time\" command", + } + } + + duration, err := InProcessArgon2KDF().Time(input.Mode, costParams) + if err != nil { + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorUnexpected, + ErrorString: fmt.Sprintf("cannot run time command: %v", err), + } + } + return &Argon2RemoteOutput{ + Command: input.Command, + Duration: duration, + } + default: + return &Argon2RemoteOutput{ + Command: input.Command, + ErrorType: Argon2RemoteErrorInvalidCommand, + ErrorString: fmt.Sprintf("invalid command: %q", string(input.Command)), + } + } +} + +// WaitAndRunArgon2RequestInRemoteProcess waits for a [Argon2RemoteInput] request on the +// supplied io.Reader before running it and sending a [Argon2RemoteOutput] response back via +// the supplied io.Writer. These will generally be connected to the process's os.Stdin and +// os.Stdout when using - certainly when using [NewRemoteArgon2KDF] on the parent side. +// This function can only be called once in a process. Subsequent calls in the same +// process will result in an error response being returned. +func WaitAndRunArgon2RequestInRemoteProcess(in io.Reader, out io.Writer) error { + var input *Argon2RemoteInput + dec := json.NewDecoder(in) + dec.DisallowUnknownFields() + if err := dec.Decode(&input); err != nil { + return fmt.Errorf("cannot decode input: %w", err) + } + + output := RunArgon2RequestInRemoteProcess(input) + + enc := json.NewEncoder(out) + if err := enc.Encode(output); err != nil { + return fmt.Errorf("cannot encode output: %w", err) + } + + return nil +} + +// remoteArgon2KDFImpl is an Argon2KDFImpl that runs the KDF in a remote process, +// using the remote JSON protocol defined in this package. +type remoteArgon2KDFImpl struct { + newRemoteCommand func() (*exec.Cmd, error) +} + +func (k *remoteArgon2KDFImpl) runInRemoteProcess(params *Argon2RemoteInput) (res *Argon2RemoteOutput, err error) { + cmd, err := k.newRemoteCommand() + if err != nil { + return nil, fmt.Errorf("cannot create remote process: %w", err) + } + + stdinPipe, err := cmd.StdinPipe() + if err != nil { + // This doesn't fail once the OS pipe is created, so there's no + // cleanup to do on failure paths. + return nil, fmt.Errorf("cannot create stdin pipe: %w", err) + } + stdoutPipe, err := cmd.StdoutPipe() + if err != nil { + // This doesn't fail once the OS pipe is created, so there's no + // cleanup to do on failure paths. + return nil, fmt.Errorf("cannot create stdout pipe: %w", err) + } + + if err := cmd.Start(); err != nil { + return nil, fmt.Errorf("cannot start remote process: %w", err) + } + defer func() { + // Run Cmd.Wait in a defer so that we shut down on error paths too, + // and we capture the Wait error if there was no other error. + waitErr := cmd.Wait() + if waitErr != nil && err == nil { + res = nil + err = fmt.Errorf("cannot wait for remote process to finish: %w", waitErr) + } + }() + + // Send the input params to the remote process. + enc := json.NewEncoder(stdinPipe) + if err := enc.Encode(params); err != nil { + return nil, fmt.Errorf("cannot encode parameters: %w", err) + } + + // Wait for thre result from the remote process. + dec := json.NewDecoder(stdoutPipe) + if err := dec.Decode(&res); err != nil { + return nil, fmt.Errorf("cannot decode result: %w", err) + } + + return res, nil +} + +func (k *remoteArgon2KDFImpl) Derive(passphrase string, salt []byte, mode Argon2Mode, params *Argon2CostParams, keyLen uint32) (key []byte, err error) { + remoteParams := &Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: passphrase, + Salt: salt, + Keylen: keyLen, + Mode: mode, + Time: params.Time, + MemoryKiB: params.MemoryKiB, + Threads: params.Threads, + } + res, err := k.runInRemoteProcess(remoteParams) + if err != nil { + return nil, err + } + if res.Err() != nil { + return nil, res.Err() + } + return res.Key, nil +} + +func (k *remoteArgon2KDFImpl) Time(mode Argon2Mode, params *Argon2CostParams) (duration time.Duration, err error) { + remoteParams := &Argon2RemoteInput{ + Command: Argon2RemoteCommandTime, + Mode: mode, + Time: params.Time, + MemoryKiB: params.MemoryKiB, + Threads: params.Threads, + } + res, err := k.runInRemoteProcess(remoteParams) + if err != nil { + return 0, err + } + if res.Err() != nil { + return 0, res.Err() + } + return res.Duration, nil +} + +// NewRemoteArgon2KDF returns a new Argon2KDF that runs each KDF invocation in a +// short-lived remote process, using a *[exec.Cmd] created by the supplied function, +// and using a protocol compatibile with [WaitAndRunArgon2RequestInRemoteProcess] +// in the remote process. +// +// The supplied function must not start the process, nor should it set the Stdin or +// Stdout fields of the [exec.Cmd] structure, as 2 pipes will be created for sending +// and receiving, and these will be connected to stdin and stdout of the remote process. +func NewRemoteArgon2KDF(newRemoteCommand func() (*exec.Cmd, error)) Argon2KDF { + return &remoteArgon2KDFImpl{ + newRemoteCommand: newRemoteCommand, + } +} diff --git a/argon2_remote_support_test.go b/argon2_remote_support_test.go new file mode 100644 index 00000000..c17a6b89 --- /dev/null +++ b/argon2_remote_support_test.go @@ -0,0 +1,643 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ + +package secboot_test + +import ( + "errors" + "os" + "os/exec" + "path/filepath" + "runtime" + + . "github.com/snapcore/secboot" + "github.com/snapcore/secboot/internal/argon2" + "github.com/snapcore/secboot/internal/testutil" + . "gopkg.in/check.v1" +) + +type argon2RemoteSupportSuite struct{} + +func (s *argon2RemoteSupportSuite) TearDownTest(c *C) { + ClearIsArgon2RemoteProcess() +} + +var _ = Suite(&argon2RemoteSupportSuite{}) + +func (s *argon2RemoteSupportSuite) TestInProcessKDFDeriveNotSupported(c *C) { + _, err := InProcessArgon2KDF().Derive("foo", nil, Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 1}, 32) + c.Check(err, ErrorMatches, `no argon2 KDF: please call secboot.SetIsArgon2RemoteProcess if the intention is to run Argon2 directly in this process`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFDeriveNoParams(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Derive("foo", nil, Argon2id, nil, 32) + c.Check(err, ErrorMatches, `nil params`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFDeriveInvalidMode(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Derive("foo", nil, Argon2Default, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 1}, 32) + c.Check(err, ErrorMatches, `invalid mode`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFDeriveInvalidTime(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Derive("foo", nil, Argon2id, &Argon2CostParams{Time: 0, MemoryKiB: 32, Threads: 1}, 32) + c.Check(err, ErrorMatches, `invalid time cost`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFDeriveInvalidThreads(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Derive("foo", nil, Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 0}, 32) + c.Check(err, ErrorMatches, `invalid number of threads`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFTimeNotSupported(c *C) { + _, err := InProcessArgon2KDF().Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 1}) + c.Check(err, ErrorMatches, `no argon2 KDF: please call secboot.SetIsArgon2RemoteProcess if the intention is to run Argon2 directly in this process`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFTimeNoParams(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Time(Argon2id, nil) + c.Check(err, ErrorMatches, `nil params`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFTimeInvalidMode(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Time(Argon2Default, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 1}) + c.Check(err, ErrorMatches, `invalid mode`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFTimeInvalidTime(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Time(Argon2id, &Argon2CostParams{Time: 0, MemoryKiB: 32, Threads: 1}) + c.Check(err, ErrorMatches, `invalid time cost`) +} + +func (s *argon2RemoteSupportSuite) TestInProcessKDFTimeInvalidThreads(c *C) { + SetIsArgon2RemoteProcess() + _, err := InProcessArgon2KDF().Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 0}) + c.Check(err, ErrorMatches, `invalid number of threads`) +} + +func (s *argon2RemoteSupportSuite) TestRunArgon2RequestInRemoteProcessInvalidProcess(c *C) { + out := RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: nil, + Keylen: 32, + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 4, + }) + c.Check(out, DeepEquals, &Argon2RemoteOutput{ + Command: Argon2RemoteCommandDerive, + ErrorType: Argon2RemoteErrorProcessNotConfigured, + ErrorString: "cannot run in a process that isn't configured as an Argon2 remote process", + }) +} + +func (s *argon2RemoteSupportSuite) TestRunArgon2RequestInRemoteProcessInvalidMode(c *C) { + SetIsArgon2RemoteProcess() + out := RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: nil, + Keylen: 32, + Mode: Argon2Mode("foo"), + Time: 4, + MemoryKiB: 32, + Threads: 4, + }) + c.Check(out, DeepEquals, &Argon2RemoteOutput{ + Command: Argon2RemoteCommandDerive, + ErrorType: Argon2RemoteErrorInvalidMode, + ErrorString: "invalid mode: \"foo\"", + }) +} + +func (s *argon2RemoteSupportSuite) TestRunArgon2RequestInRemoteProcessInvalidTime(c *C) { + SetIsArgon2RemoteProcess() + out := RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: nil, + Keylen: 32, + Mode: Argon2id, + Time: 0, + MemoryKiB: 32, + Threads: 4, + }) + c.Check(out, DeepEquals, &Argon2RemoteOutput{ + Command: Argon2RemoteCommandDerive, + ErrorType: Argon2RemoteErrorInvalidTimeCost, + ErrorString: "invalid time cost: cannot be zero", + }) +} + +func (s *argon2RemoteSupportSuite) TestRunArgon2RequestInRemoteProcessInvalidThreads(c *C) { + SetIsArgon2RemoteProcess() + out := RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: nil, + Keylen: 32, + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 0, + }) + c.Check(out, DeepEquals, &Argon2RemoteOutput{ + Command: Argon2RemoteCommandDerive, + ErrorType: Argon2RemoteErrorInvalidThreads, + ErrorString: "invalid threads: cannot be zero", + }) +} + +func (s *argon2RemoteSupportSuite) TestArgon2RemoteOutputErr(c *C) { + out := &Argon2RemoteOutput{ + Command: Argon2RemoteCommandDerive, + ErrorType: Argon2RemoteErrorProcessNotConfigured, + ErrorString: "cannot run in a process that isn't configured as an Argon2 remote process", + } + err := out.Err() + c.Check(err, ErrorMatches, `cannot process KDF request: process-not-configured \(cannot run in a process that isn't configured as an Argon2 remote process\)`) + var e *Argon2RemoteError + c.Check(errors.As(err, &e), testutil.IsTrue) +} + +type argon2RemoteSupportSuiteExpensive struct { + runArgon2RemoteDir string +} + +func (s *argon2RemoteSupportSuiteExpensive) runArgon2RemotePath() string { + return filepath.Join(s.runArgon2RemoteDir, "run_argon2") +} + +func (s *argon2RemoteSupportSuiteExpensive) SetUpSuite(c *C) { + if _, exists := os.LookupEnv("NO_ARGON2_TESTS"); exists { + c.Skip("skipping expensive argon2 tests") + } + s.runArgon2RemoteDir = c.MkDir() + cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), "build", "-o", s.runArgon2RemoteDir, "./cmd/run_argon2") + c.Assert(cmd.Run(), IsNil) +} + +func (s *argon2RemoteSupportSuiteExpensive) SetUpTest(c *C) { + SetIsArgon2RemoteProcess() +} + +func (s *argon2RemoteSupportSuiteExpensive) TearDownTest(c *C) { + ClearIsArgon2RemoteProcess() +} + +var _ = Suite(&argon2RemoteSupportSuiteExpensive{}) + +type testInProcessArgon2KDFDeriveData struct { + passphrase string + salt []byte + mode Argon2Mode + params *Argon2CostParams + keyLen uint32 + + expectedKey []byte +} + +func (s *argon2RemoteSupportSuiteExpensive) testInProcessKDFDerive(c *C, data *testInProcessArgon2KDFDeriveData) { + key, err := InProcessArgon2KDF().Derive(data.passphrase, data.salt, data.mode, data.params, data.keyLen) + c.Check(err, IsNil) + c.Check(key, DeepEquals, data.expectedKey) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestInProcessKDFDerive(c *C) { + s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "cbd85bef66eae997ed1f8f7f3b1d5bec09425f72789f5113d0215bb8bdc6891f"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestInProcessKDFDeriveDifferentPassphrase(c *C) { + s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ + passphrase: "bar", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "19b17adfb811233811b9e5872165803d01e81d3951e73b996a40c49b15c6e532"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestInProcessKDFiDeriveDifferentSalt(c *C) { + s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ + passphrase: "foo", + salt: []byte("zyxwtsrqponmlkjihgfedcba987654"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "b5cf92c57c00f2a1d0de9d46ba0acef0e37ad1d4807b45b2dad1a50e797cc96d"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestInProcessKDFDeriveDifferentMode(c *C) { + s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2i, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "60b6d0ab8d4c39b4f17a7c05486c714097d2bf1f1d85c6d5fad4fe24171003fe"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestInProcessKDFDeriveDifferentParams(c *C) { + s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 48, + MemoryKiB: 32 * 1024, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "f83001f90fbbc24823773e56f65eeace261285ab7e1394efeb8348d2184c240c"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestInProcessKDFDeriveDifferentKeyLen(c *C) { + s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 64, + expectedKey: testutil.DecodeHexString(c, "dc8b7ed604470a49d983f86b1574b8619631ccd0282f591b227c153ce200f395615e7ddb5b01026edbf9bf7105ca2de294d67f69d9678e65417d59e51566e746"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestInProcessKDFTime(c *C) { + time1, err := InProcessArgon2KDF().Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32 * 1024, Threads: 4}) + c.Check(err, IsNil) + + runtime.GC() + time2, err := InProcessArgon2KDF().Time(Argon2id, &Argon2CostParams{Time: 16, MemoryKiB: 32 * 1024, Threads: 4}) + c.Check(err, IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(time2 > time1, testutil.IsTrue) + + runtime.GC() + time2, err = InProcessArgon2KDF().Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 128 * 1024, Threads: 4}) + c.Check(err, IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(time2 > time1, testutil.IsTrue) + + runtime.GC() + time2, err = InProcessArgon2KDF().Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32 * 1024, Threads: 1}) + c.Check(err, IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(time2 > time1, testutil.IsTrue) +} + +func (s *argon2RemoteSupportSuiteExpensive) testRunArgon2RequestInRemoteProcessDerive(c *C, input *Argon2RemoteInput) { + res := RunArgon2RequestInRemoteProcess(input) + c.Check(res.Command, Equals, Argon2RemoteCommandDerive) + c.Check(res.Err(), IsNil) + + runtime.GC() + + expected := argon2.Key(input.Passphrase, input.Salt, argon2.Mode(input.Mode), &argon2.CostParams{ + Time: input.Time, + MemoryKiB: input.MemoryKiB, + Threads: input.Threads}, input.Keylen) + c.Check(expected, DeepEquals, res.Key) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessDerive(c *C) { + s.testRunArgon2RequestInRemoteProcessDerive(c, &Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: []byte("0123456789abcdefghijklmnopqrstuv"), + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 4, + Keylen: 32, + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessDeriveDifferentPassphrase(c *C) { + s.testRunArgon2RequestInRemoteProcessDerive(c, &Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "bar", + Salt: []byte("0123456789abcdefghijklmnopqrstuv"), + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 4, + Keylen: 32, + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessDeriveDifferentSalt(c *C) { + s.testRunArgon2RequestInRemoteProcessDerive(c, &Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: []byte("zyxwtsrqponmlkjihgfedcba987654"), + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 4, + Keylen: 32, + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessDeriveDifferentMode(c *C) { + s.testRunArgon2RequestInRemoteProcessDerive(c, &Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: []byte("0123456789abcdefghijklmnopqrstuv"), + Mode: Argon2i, + Time: 4, + MemoryKiB: 32, + Threads: 4, + Keylen: 32, + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessDeriveDifferentParams(c *C) { + s.testRunArgon2RequestInRemoteProcessDerive(c, &Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: []byte("0123456789abcdefghijklmnopqrstuv"), + Mode: Argon2id, + Time: 48, + MemoryKiB: 32 * 1024, + Threads: 4, + Keylen: 32, + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessDeriveDifferentKeylen(c *C) { + s.testRunArgon2RequestInRemoteProcessDerive(c, &Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: []byte("0123456789abcdefghijklmnopqrstuv"), + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 4, + Keylen: 64, + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessTime(c *C) { + res := RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandTime, + Mode: Argon2id, + Time: 4, + MemoryKiB: 32 * 1024, + Threads: 4, + }) + c.Check(res.Err(), IsNil) + + ClearIsArgon2RemoteProcess() + SetIsArgon2RemoteProcess() + res2 := RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandTime, + Mode: Argon2id, + Time: 16, + MemoryKiB: 32 * 1024, + Threads: 4, + }) + c.Check(res2.Err(), IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(res2.Duration > res.Duration, testutil.IsTrue) + + ClearIsArgon2RemoteProcess() + SetIsArgon2RemoteProcess() + res2 = RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandTime, + Mode: Argon2id, + Time: 4, + MemoryKiB: 128 * 1024, + Threads: 4, + }) + c.Check(res2.Err(), IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(res2.Duration > res.Duration, testutil.IsTrue) + + ClearIsArgon2RemoteProcess() + SetIsArgon2RemoteProcess() + res2 = RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandTime, + Mode: Argon2id, + Time: 4, + MemoryKiB: 32 * 1024, + Threads: 1, + }) + c.Check(res2.Err(), IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(res2.Duration > res.Duration, testutil.IsTrue) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRunArgon2RequestInRemoteProcessConsumedProcess(c *C) { + out := RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: nil, + Keylen: 32, + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 4, + }) + c.Check(out, NotNil) + + out = RunArgon2RequestInRemoteProcess(&Argon2RemoteInput{ + Command: Argon2RemoteCommandDerive, + Passphrase: "foo", + Salt: nil, + Keylen: 32, + Mode: Argon2id, + Time: 4, + MemoryKiB: 32, + Threads: 4, + }) + c.Check(out, DeepEquals, &Argon2RemoteOutput{ + Command: Argon2RemoteCommandDerive, + ErrorType: Argon2RemoteErrorConsumedProcess, + ErrorString: "cannot run more than once in the same process", + }) +} + +type testRemoteArgon2DeriveParams struct { + passphrase string + salt []byte + mode Argon2Mode + params *Argon2CostParams + keyLen uint32 + expectedKey []byte +} + +func (s *argon2RemoteSupportSuiteExpensive) testRemoteArgon2Derive(c *C, params *testRemoteArgon2DeriveParams) { + kdf := NewRemoteArgon2KDF(func() (*exec.Cmd, error) { + return exec.Command(s.runArgon2RemotePath()), nil + }) + key, err := kdf.Derive(params.passphrase, params.salt, params.mode, params.params, params.keyLen) + c.Check(err, IsNil) + c.Check(key, DeepEquals, params.expectedKey) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRemoteArgon2Derive(c *C) { + s.testRemoteArgon2Derive(c, &testRemoteArgon2DeriveParams{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "cbd85bef66eae997ed1f8f7f3b1d5bec09425f72789f5113d0215bb8bdc6891f"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRemoteArgon2DeriveDifferentPassphrase(c *C) { + s.testRemoteArgon2Derive(c, &testRemoteArgon2DeriveParams{ + passphrase: "bar", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "19b17adfb811233811b9e5872165803d01e81d3951e73b996a40c49b15c6e532"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRemoteArgon2DeriveDifferentSalt(c *C) { + s.testRemoteArgon2Derive(c, &testRemoteArgon2DeriveParams{ + passphrase: "foo", + salt: []byte("zyxwtsrqponmlkjihgfedcba987654"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "b5cf92c57c00f2a1d0de9d46ba0acef0e37ad1d4807b45b2dad1a50e797cc96d"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRemoteArgon2DeriveDifferentMode(c *C) { + s.testRemoteArgon2Derive(c, &testRemoteArgon2DeriveParams{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2i, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "60b6d0ab8d4c39b4f17a7c05486c714097d2bf1f1d85c6d5fad4fe24171003fe"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRemoteArgon2DeriveDifferentParams(c *C) { + s.testRemoteArgon2Derive(c, &testRemoteArgon2DeriveParams{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 48, + MemoryKiB: 32 * 1024, + Threads: 4}, + keyLen: 32, + expectedKey: testutil.DecodeHexString(c, "f83001f90fbbc24823773e56f65eeace261285ab7e1394efeb8348d2184c240c"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRemoteArgon2DeriveDifferentKeyLen(c *C) { + s.testRemoteArgon2Derive(c, &testRemoteArgon2DeriveParams{ + passphrase: "foo", + salt: []byte("0123456789abcdefghijklmnopqrstuv"), + mode: Argon2id, + params: &Argon2CostParams{ + Time: 4, + MemoryKiB: 32, + Threads: 4}, + keyLen: 64, + expectedKey: testutil.DecodeHexString(c, "dc8b7ed604470a49d983f86b1574b8619631ccd0282f591b227c153ce200f395615e7ddb5b01026edbf9bf7105ca2de294d67f69d9678e65417d59e51566e746"), + }) +} + +func (s *argon2RemoteSupportSuiteExpensive) TestRemoteArgon2Time(c *C) { + kdf := NewRemoteArgon2KDF(func() (*exec.Cmd, error) { + return exec.Command(s.runArgon2RemotePath()), nil + }) + + time1, err := kdf.Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32 * 1024, Threads: 4}) + c.Check(err, IsNil) + + time2, err := kdf.Time(Argon2id, &Argon2CostParams{Time: 16, MemoryKiB: 32 * 1024, Threads: 4}) + c.Check(err, IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(time2 > time1, testutil.IsTrue) + + time2, err = kdf.Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 128 * 1024, Threads: 4}) + c.Check(err, IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(time2 > time1, testutil.IsTrue) + + time2, err = kdf.Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32 * 1024, Threads: 1}) + c.Check(err, IsNil) + // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with + // types of int64 kind + c.Check(time2 > time1, testutil.IsTrue) +} diff --git a/argon2_test.go b/argon2_test.go index 34cc8ba0..ef9c5280 100644 --- a/argon2_test.go +++ b/argon2_test.go @@ -21,7 +21,6 @@ package secboot_test import ( "math" - "os" "runtime" "time" @@ -32,7 +31,6 @@ import ( . "gopkg.in/check.v1" . "github.com/snapcore/secboot" - "github.com/snapcore/secboot/internal/argon2" "github.com/snapcore/secboot/internal/testutil" ) @@ -248,174 +246,3 @@ func (s *argon2Suite) TestKDFParamsInvalidMemoryKiB(c *C) { _, err := opts.KdfParams(0) c.Check(err, ErrorMatches, `invalid memory cost 4294967295KiB`) } - -func (s *argon2Suite) TestInProcessKDFDeriveInvalidMode(c *C) { - _, err := InProcessArgon2KDF.Derive("foo", nil, Argon2Default, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 1}, 32) - c.Check(err, ErrorMatches, `invalid mode`) -} - -func (s *argon2Suite) TestInProcessKDFDeriveInvalidParams(c *C) { - _, err := InProcessArgon2KDF.Derive("foo", nil, Argon2id, nil, 32) - c.Check(err, ErrorMatches, `nil params`) -} - -func (s *argon2Suite) TestInProcessKDFDeriveInvalidTime(c *C) { - _, err := InProcessArgon2KDF.Derive("foo", nil, Argon2id, &Argon2CostParams{Time: 0, MemoryKiB: 32, Threads: 1}, 32) - c.Check(err, ErrorMatches, `invalid time cost`) -} - -func (s *argon2Suite) TestInProcessKDFDeriveInvalidThreads(c *C) { - _, err := InProcessArgon2KDF.Derive("foo", nil, Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 0}, 32) - c.Check(err, ErrorMatches, `invalid number of threads`) -} - -func (s *argon2Suite) TestInProcessKDFTimeInvalidMode(c *C) { - _, err := InProcessArgon2KDF.Time(Argon2Default, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 1}) - c.Check(err, ErrorMatches, `invalid mode`) -} - -func (s *argon2Suite) TestInProcessKDFTimeInvalidParams(c *C) { - _, err := InProcessArgon2KDF.Time(Argon2id, nil) - c.Check(err, ErrorMatches, `nil params`) -} - -func (s *argon2Suite) TestInProcessKDFTimeInvalidTime(c *C) { - _, err := InProcessArgon2KDF.Time(Argon2id, &Argon2CostParams{Time: 0, MemoryKiB: 32, Threads: 1}) - c.Check(err, ErrorMatches, `invalid time cost`) -} - -func (s *argon2Suite) TestInProcessKDFTimeInvalidThreads(c *C) { - _, err := InProcessArgon2KDF.Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32, Threads: 0}) - c.Check(err, ErrorMatches, `invalid number of threads`) -} - -type argon2SuiteExpensive struct{} - -func (s *argon2SuiteExpensive) SetUpSuite(c *C) { - if _, exists := os.LookupEnv("NO_ARGON2_TESTS"); exists { - c.Skip("skipping expensive argon2 tests") - } -} - -var _ = Suite(&argon2SuiteExpensive{}) - -type testInProcessArgon2KDFDeriveData struct { - passphrase string - salt []byte - mode Argon2Mode - params *Argon2CostParams - keyLen uint32 -} - -func (s *argon2SuiteExpensive) testInProcessKDFDerive(c *C, data *testInProcessArgon2KDFDeriveData) { - key, err := InProcessArgon2KDF.Derive(data.passphrase, data.salt, data.mode, data.params, data.keyLen) - c.Check(err, IsNil) - runtime.GC() - - expected := argon2.Key(data.passphrase, data.salt, argon2.Mode(data.mode), &argon2.CostParams{ - Time: data.params.Time, - MemoryKiB: data.params.MemoryKiB, - Threads: data.params.Threads}, data.keyLen) - runtime.GC() - - c.Check(key, DeepEquals, expected) -} - -func (s *argon2SuiteExpensive) TestInProcessKDFDerive(c *C) { - s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ - passphrase: "foo", - salt: []byte("0123456789abcdefghijklmnopqrstuv"), - mode: Argon2id, - params: &Argon2CostParams{ - Time: 4, - MemoryKiB: 32, - Threads: 4}, - keyLen: 32}) -} - -func (s *argon2SuiteExpensive) TestInProcessKDFDeriveDifferentPassphrase(c *C) { - s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ - passphrase: "bar", - salt: []byte("0123456789abcdefghijklmnopqrstuv"), - mode: Argon2id, - params: &Argon2CostParams{ - Time: 4, - MemoryKiB: 32, - Threads: 4}, - keyLen: 32}) -} - -func (s *argon2SuiteExpensive) TestInProcessKDFiDeriveDifferentSalt(c *C) { - s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ - passphrase: "foo", - salt: []byte("zyxwvutsrqponmlkjihgfedcba987654"), - mode: Argon2id, - params: &Argon2CostParams{ - Time: 4, - MemoryKiB: 32, - Threads: 4}, - keyLen: 32}) -} - -func (s *argon2SuiteExpensive) TestInProcessKDFDeriveDifferentMode(c *C) { - s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ - passphrase: "foo", - salt: []byte("0123456789abcdefghijklmnopqrstuv"), - mode: Argon2i, - params: &Argon2CostParams{ - Time: 4, - MemoryKiB: 32, - Threads: 4}, - keyLen: 32}) -} - -func (s *argon2SuiteExpensive) TestInProcessKDFDeriveDifferentParams(c *C) { - s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ - passphrase: "foo", - salt: []byte("0123456789abcdefghijklmnopqrstuv"), - mode: Argon2id, - params: &Argon2CostParams{ - Time: 48, - MemoryKiB: 32 * 1024, - Threads: 4}, - keyLen: 32}) -} - -func (s *argon2SuiteExpensive) TestInProcessKDFDeriveDifferentKeyLen(c *C) { - s.testInProcessKDFDerive(c, &testInProcessArgon2KDFDeriveData{ - passphrase: "foo", - salt: []byte("0123456789abcdefghijklmnopqrstuv"), - mode: Argon2id, - params: &Argon2CostParams{ - Time: 4, - MemoryKiB: 32, - Threads: 4}, - keyLen: 64}) -} - -func (s *argon2SuiteExpensive) TestInProcessKDFTime(c *C) { - time1, err := InProcessArgon2KDF.Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32 * 1024, Threads: 4}) - runtime.GC() - c.Check(err, IsNil) - - time2, err := InProcessArgon2KDF.Time(Argon2id, &Argon2CostParams{Time: 16, MemoryKiB: 32 * 1024, Threads: 4}) - runtime.GC() - c.Check(err, IsNil) - // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with - // types of int64 kind - c.Check(time2 > time1, testutil.IsTrue) - - time2, err = InProcessArgon2KDF.Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 128 * 1024, Threads: 4}) - runtime.GC() - c.Check(err, IsNil) - // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with - // types of int64 kind - c.Check(time2 > time1, testutil.IsTrue) - - time2, err = InProcessArgon2KDF.Time(Argon2id, &Argon2CostParams{Time: 4, MemoryKiB: 32 * 1024, Threads: 1}) - runtime.GC() - c.Check(err, IsNil) - // XXX: this needs a checker like go-tpm2/testutil's IntGreater, which copes with - // types of int64 kind - c.Check(time2 > time1, testutil.IsTrue) -} diff --git a/cmd/run_argon2/main.go b/cmd/run_argon2/main.go new file mode 100644 index 00000000..c23334a4 --- /dev/null +++ b/cmd/run_argon2/main.go @@ -0,0 +1,50 @@ +// -*- Mode: Go; indent-tabs-mode: t -*- + +/* + * Copyright (C) 2024-2024 Canonical Ltd + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 3 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + */ +package main + +import ( + "errors" + "fmt" + "os" + + "github.com/snapcore/secboot" +) + +func run() error { + if len(os.Args) != 1 { + return errors.New("usage: echo | run_argon2") + } + + secboot.SetIsArgon2RemoteProcess() + + err := secboot.WaitAndRunArgon2RequestInRemoteProcess(os.Stdin, os.Stdout) + if err != nil { + return fmt.Errorf("cannot run request: %w", err) + } + + return nil +} + +func main() { + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } + os.Exit(0) +} diff --git a/export_test.go b/export_test.go index a284fddf..635c8c77 100644 --- a/export_test.go +++ b/export_test.go @@ -21,6 +21,8 @@ package secboot import ( "io" + "runtime" + "sync/atomic" "github.com/snapcore/secboot/internal/luks2" "github.com/snapcore/secboot/internal/luksview" @@ -148,6 +150,11 @@ func MockHashAlgAvailable() (restore func()) { } } +func ClearIsArgon2RemoteProcess() { + atomic.StoreUint32(&argon2RemoteProcessStatus, notArgon2RemoteProcess) + runtime.GC() +} + func (d *KeyData) DerivePassphraseKeys(passphrase string) (key, iv, auth []byte, err error) { return d.derivePassphraseKeys(passphrase) }