-
Notifications
You must be signed in to change notification settings - Fork 598
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmd, secboot: add argon2 out-of-process kdf support #15073
Changes from 7 commits
11223a9
ed24159
4d9e5c0
6df58d8
02ca933
3e1485d
ce2f485
3cebcc4
b52bbd2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,23 @@ | ||||||||||||||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||||||||||||||
Check failure on line 1 in secboot/argon2_out_of_process_dummy.go
|
||||||||||||||
//go:build nosecboot | ||||||||||||||
|
||||||||||||||
/* | ||||||||||||||
* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>. | ||||||||||||||
* | ||||||||||||||
*/ | ||||||||||||||
|
||||||||||||||
package secboot | ||||||||||||||
|
||||||||||||||
func MaybeRunArgon2OutOfProcessRequestHandler() {} | ||||||||||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it should panic when There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, my bad, I copied directly without changing it |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
//go:build !nosecboot | ||
|
||
/* | ||
* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package secboot | ||
|
||
import ( | ||
"fmt" | ||
"os" | ||
"os/exec" | ||
"time" | ||
|
||
sb "github.com/snapcore/secboot" | ||
|
||
"github.com/snapcore/snapd/logger" | ||
) | ||
|
||
var ( | ||
osExit = os.Exit | ||
osReadlink = os.Readlink | ||
|
||
sbWaitForAndRunArgon2OutOfProcessRequest = sb.WaitForAndRunArgon2OutOfProcessRequest | ||
sbNewOutOfProcessArgon2KDF = sb.NewOutOfProcessArgon2KDF | ||
sbSetArgon2KDF = sb.SetArgon2KDF | ||
) | ||
|
||
const ( | ||
outOfProcessArgon2KDFTimeout = 100 * time.Millisecond | ||
outOfProcessArgon2Arg = "--argon2-proc" | ||
) | ||
|
||
func isOutOfProcessArgon2KDFMode() bool { | ||
return len(os.Args) >= 2 && os.Args[1] == outOfProcessArgon2Arg | ||
} | ||
|
||
// MaybeRunArgon2OutOfProcessRequestHandler is supposed to be called | ||
// from the main() of binaries involved with sealing/unsealing of | ||
// keys (i.e. snapd and snap-bootstrap). | ||
// | ||
// This switches the binary to a special argon2 mode when the --argon2-proc arg | ||
// is detected where it acts as an argon2 out-of-process helper command and | ||
// exits when its work is done, otherwise (in normal mode) it sets the default | ||
// argon2 kdf implementation be self-invoking into the special argon2 mode of | ||
// the calling binary. | ||
// | ||
// For more context, check docs for sb.WaitForAndRunArgon2OutOfProcessRequest | ||
// and sb.NewOutOfProcessArgon2KDF for details on how the flow works | ||
// in secboot. | ||
func MaybeRunArgon2OutOfProcessRequestHandler() { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What if we change the API a little such that it can be used like so, in snapd:
and snap-bootstrap:
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. that's a lot of extra code that I'm not keen on. Maybe the compromise is to pass the marker flag to the MaybeRunArgon2OutOfProcessRequestHandler |
||
if !isOutOfProcessArgon2KDFMode() { | ||
// Binary was invoked in normal mode, let's setup default argon2 kdf implementation | ||
// to point to this binary when invoked using special args. | ||
exe, err := osReadlink("/proc/self/exe") | ||
if err != nil { | ||
logger.Noticef("internal error: failed to read symlink of /proc/self/exe: %v", err) | ||
return | ||
} | ||
|
||
handlerCmd := func() (*exec.Cmd, error) { | ||
cmd := exec.Command(exe, outOfProcessArgon2Arg) | ||
return cmd, nil | ||
} | ||
argon2KDF := sbNewOutOfProcessArgon2KDF(handlerCmd, outOfProcessArgon2KDFTimeout, nil) | ||
sbSetArgon2KDF(argon2KDF) | ||
|
||
return | ||
pedronis marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
logger.Noticef("running argon2 out-of-process helper") | ||
// Ignore the lock release callback and use implicit release on process termination. | ||
_, err := sbWaitForAndRunArgon2OutOfProcessRequest(os.Stdin, os.Stdout, sb.NoArgon2OutOfProcessWatchdogHandler()) | ||
if err != nil { | ||
fmt.Fprintf(os.Stderr, "cannot run argon2 out-of-process request: %v", err) | ||
osExit(1) | ||
} | ||
|
||
// Argon2 request was processed successfully | ||
osExit(0) | ||
|
||
panic("internal error: not reachable") | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
// -*- Mode: Go; indent-tabs-mode: t -*- | ||
//go:build !nosecboot | ||
|
||
/* | ||
* Copyright (C) 2025 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 <http://www.gnu.org/licenses/>. | ||
* | ||
*/ | ||
|
||
package secboot_test | ||
|
||
import ( | ||
"errors" | ||
"io" | ||
"os/exec" | ||
"time" | ||
|
||
sb "github.com/snapcore/secboot" | ||
. "gopkg.in/check.v1" | ||
|
||
"github.com/snapcore/snapd/secboot" | ||
) | ||
|
||
type argon2Suite struct { | ||
} | ||
|
||
var _ = Suite(&argon2Suite{}) | ||
|
||
func (*argon2Suite) TestMaybeRunArgon2OutOfProcessRequestHandlerArgon2Mode(c *C) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. tests might need to be renamed now |
||
runArgon2Called := 0 | ||
restore := secboot.MockSbWaitForAndRunArgon2OutOfProcessRequest(func(_ io.Reader, _ io.WriteCloser, _ sb.Argon2OutOfProcessWatchdogHandler) (lockRelease func(), err error) { | ||
runArgon2Called++ | ||
return nil, nil | ||
}) | ||
defer restore() | ||
|
||
setArgon2Called := 0 | ||
restore = secboot.MockSbSetArgon2KDF(func(kdf sb.Argon2KDF) sb.Argon2KDF { | ||
setArgon2Called++ | ||
return nil | ||
}) | ||
defer restore() | ||
|
||
exitCalled := 0 | ||
restore = secboot.MockOsExit(func(code int) { | ||
exitCalled++ | ||
c.Assert(code, Equals, 0) | ||
panic("os.Exit(0)") | ||
}) | ||
defer restore() | ||
|
||
restore = secboot.MockOsArgs([]string{"/path/to/cmd", "--argon2-proc"}) | ||
defer restore() | ||
|
||
// Since we override os.Exit(0), we expect to panic (injected above) | ||
c.Assert(secboot.MaybeRunArgon2OutOfProcessRequestHandler, Panics, "os.Exit(0)") | ||
|
||
c.Check(setArgon2Called, Equals, 0) | ||
c.Check(runArgon2Called, Equals, 1) | ||
c.Check(exitCalled, Equals, 1) | ||
} | ||
|
||
func (*argon2Suite) TestMaybeRunArgon2OutOfProcessRequestHandlerArgon2ModeError(c *C) { | ||
runArgon2Called := 0 | ||
restore := secboot.MockSbWaitForAndRunArgon2OutOfProcessRequest(func(_ io.Reader, _ io.WriteCloser, _ sb.Argon2OutOfProcessWatchdogHandler) (lockRelease func(), err error) { | ||
runArgon2Called++ | ||
return nil, errors.New("boom!") | ||
}) | ||
defer restore() | ||
|
||
setArgon2Called := 0 | ||
restore = secboot.MockSbSetArgon2KDF(func(kdf sb.Argon2KDF) sb.Argon2KDF { | ||
setArgon2Called++ | ||
return nil | ||
}) | ||
defer restore() | ||
|
||
exitCalled := 0 | ||
restore = secboot.MockOsExit(func(code int) { | ||
exitCalled++ | ||
c.Assert(code, Equals, 1) | ||
panic("os.Exit(1)") | ||
}) | ||
defer restore() | ||
|
||
restore = secboot.MockOsArgs([]string{"/path/to/cmd", "--argon2-proc"}) | ||
defer restore() | ||
|
||
c.Assert(secboot.MaybeRunArgon2OutOfProcessRequestHandler, Panics, "os.Exit(1)") | ||
|
||
c.Check(setArgon2Called, Equals, 0) | ||
c.Check(runArgon2Called, Equals, 1) | ||
c.Check(exitCalled, Equals, 1) | ||
} | ||
|
||
type mockArgon2KDF struct{} | ||
|
||
func (*mockArgon2KDF) Derive(passphrase string, salt []byte, mode sb.Argon2Mode, params *sb.Argon2CostParams, keyLen uint32) ([]byte, error) { | ||
return nil, nil | ||
} | ||
|
||
func (*mockArgon2KDF) Time(mode sb.Argon2Mode, params *sb.Argon2CostParams) (time.Duration, error) { | ||
return 0, nil | ||
} | ||
|
||
func (*argon2Suite) TestMaybeRunArgon2OutOfProcessRequestHandlerNormalMode(c *C) { | ||
runArgon2Called := 0 | ||
restore := secboot.MockSbWaitForAndRunArgon2OutOfProcessRequest(func(_ io.Reader, _ io.WriteCloser, _ sb.Argon2OutOfProcessWatchdogHandler) (lockRelease func(), err error) { | ||
runArgon2Called++ | ||
return nil, nil | ||
}) | ||
defer restore() | ||
|
||
exitCalled := 0 | ||
restore = secboot.MockOsExit(func(code int) { | ||
exitCalled++ | ||
c.Assert(code, Equals, 0) | ||
panic("injected panic") | ||
}) | ||
defer restore() | ||
|
||
restore = secboot.MockOsReadlink(func(name string) (string, error) { | ||
c.Assert(name, Equals, "/proc/self/exe") | ||
return "/path/to/cmd", nil | ||
}) | ||
defer restore() | ||
|
||
argon2KDF := &mockArgon2KDF{} | ||
|
||
restore = secboot.MockSbNewOutOfProcessArgon2KDF(func(newHandlerCmd func() (*exec.Cmd, error), timeout time.Duration, watchdog sb.Argon2OutOfProcessWatchdogMonitor) sb.Argon2KDF { | ||
c.Check(timeout, Equals, 100*time.Millisecond) | ||
c.Check(watchdog, IsNil) | ||
|
||
cmd, err := newHandlerCmd() | ||
c.Assert(err, IsNil) | ||
c.Check(cmd.Path, Equals, "/path/to/cmd") | ||
c.Check(cmd.Args, DeepEquals, []string{"/path/to/cmd", "--argon2-proc"}) | ||
|
||
return argon2KDF | ||
}) | ||
defer restore() | ||
|
||
setArgon2Called := 0 | ||
restore = secboot.MockSbSetArgon2KDF(func(kdf sb.Argon2KDF) sb.Argon2KDF { | ||
setArgon2Called++ | ||
// Check pointer points to mock implementation | ||
c.Assert(kdf, Equals, argon2KDF) | ||
return nil | ||
}) | ||
defer restore() | ||
|
||
for _, args := range [][]string{ | ||
{}, | ||
{"/path/to/cmd"}, | ||
{"/path/to/cmd", "not-run-argon2"}, | ||
{"/path/to/cmd", "not-run-argon2", "--argon2-proc"}, | ||
} { | ||
restore := secboot.MockOsArgs(args) | ||
defer restore() | ||
// This should exit early before running the argon2 helper and calling os.Exit (and no injected panic) | ||
secboot.MaybeRunArgon2OutOfProcessRequestHandler() | ||
} | ||
|
||
c.Check(setArgon2Called, Equals, 4) | ||
c.Check(runArgon2Called, Equals, 0) | ||
c.Check(exitCalled, Equals, 0) | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this should raise an error/panic if it observes a trigger for out of process secboot runner