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)
}