diff --git a/auth_requestor.go b/auth_requestor.go
index e275c08e..0a37e7ae 100644
--- a/auth_requestor.go
+++ b/auth_requestor.go
@@ -21,6 +21,7 @@ package secboot
import (
"context"
+ "errors"
"fmt"
"strings"
)
@@ -81,6 +82,20 @@ const (
UserAuthResultInvalidFormat
)
+var ErrAuthRequestorNotAvailable = errors.New("the auth requestor is not available")
+
+// AuthRequestorStringer is used by the some implementation of [AuthRequestor] to
+// obtain translated strings.
+type AuthRequestorStringer interface {
+ // RequestUserCredentialString returns messages used by RequestUserCredential. The
+ // name is a string supplied via the WithAuthRequestorUserVisibleName option, and the
+ // path is the storage container path.
+ RequestUserCredentialString(name, path string, authTypes UserAuthType) (string, error)
+
+ // NotifyUserAuthResultString returns messages used by NotifyUserAuthResult.
+ NotifyUserAuthResultString(name, path string, result UserAuthResult, authTypes, exhaustedAuthTypes UserAuthType) (string, error)
+}
+
// AuthRequestor is an interface for requesting credentials.
type AuthRequestor interface {
// RequestUserCredential is used to request a user credential that is
diff --git a/auth_requestor_auto.go b/auth_requestor_auto.go
new file mode 100644
index 00000000..8909c6b9
--- /dev/null
+++ b/auth_requestor_auto.go
@@ -0,0 +1,96 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 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 (
+ "context"
+ "errors"
+ "fmt"
+ "io"
+)
+
+var (
+ newPlymouthAuthRequestor = NewPlymouthAuthRequestor
+ newSystemdAuthRequestor = NewSystemdAuthRequestor
+)
+
+type autoAuthRequestor struct {
+ requestors []AuthRequestor
+ lastUsed AuthRequestor
+}
+
+func (r *autoAuthRequestor) RequestUserCredential(ctx context.Context, name, path string, authTypes UserAuthType) (string, UserAuthType, error) {
+ for _, req := range r.requestors {
+ switch cred, credType, err := req.RequestUserCredential(ctx, name, path, authTypes); {
+ case err == nil:
+ r.lastUsed = req
+ fallthrough
+ case !errors.Is(err, ErrAuthRequestorNotAvailable):
+ return cred, credType, err
+ }
+ }
+
+ return "", 0, ErrAuthRequestorNotAvailable
+}
+
+func (r *autoAuthRequestor) NotifyUserAuthResult(ctx context.Context, result UserAuthResult, authTypes, exhaustedAuthTypes UserAuthType) error {
+ if r.lastUsed == nil {
+ return errors.New("no user credential requested yet")
+ }
+ return r.lastUsed.NotifyUserAuthResult(ctx, result, authTypes, exhaustedAuthTypes)
+}
+
+// NewAutoAuthRequestor creates an implementation of AuthRequestor that automatically
+// selects the first available implementation in the following order:
+// - Plymouth.
+// - systemd-ask-password.
+//
+// The caller supplies an implementation of AuthRequestorStringer that returns messages.
+// The console argument is used by the systemd-ask-password implementation of
+// [AuthRequestor.NotifyUserAuthResult] where result is not [UserAuthResultSuccess]. If not
+// provided, it defaults to [os.Stderr].
+func NewAutoAuthRequestor(stderr io.Writer, stringer AuthRequestorStringer) (AuthRequestor, error) {
+ var requestors []AuthRequestor
+ switch ply, err := newPlymouthAuthRequestor(stringer); {
+ case errors.Is(err, ErrAuthRequestorNotAvailable):
+ // ignore
+ case err != nil:
+ return nil, fmt.Errorf("cannot create Plymouth AuthRequestor: %w", err)
+ default:
+ requestors = append(requestors, ply)
+ }
+
+ switch sd, err := newSystemdAuthRequestor(stderr, func(name, path string, authTypes UserAuthType) (string, error) {
+ return stringer.RequestUserCredentialString(name, path, authTypes)
+ }); {
+ case errors.Is(err, ErrAuthRequestorNotAvailable):
+ // ignore
+ case err != nil:
+ return nil, fmt.Errorf("cannot create systemd AuthRequestor: %w", err)
+ default:
+ requestors = append(requestors, sd)
+ }
+
+ if len(requestors) == 0 {
+ return nil, ErrAuthRequestorNotAvailable
+ }
+
+ return &autoAuthRequestor{requestors: requestors}, nil
+}
diff --git a/auth_requestor_auto_test.go b/auth_requestor_auto_test.go
new file mode 100644
index 00000000..c7fbd22e
--- /dev/null
+++ b/auth_requestor_auto_test.go
@@ -0,0 +1,476 @@
+// -*- Mode: Go; indent-tabs-mode: t -*-
+
+/*
+ * Copyright (C) 2026 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 (
+ "bytes"
+ "context"
+ "errors"
+ "fmt"
+ "io"
+ "strings"
+
+ . "github.com/snapcore/secboot"
+ "github.com/snapcore/secboot/internal/testutil"
+ snapd_testutil "github.com/snapcore/snapd/testutil"
+
+ . "gopkg.in/check.v1"
+)
+
+type authRequestorAutoSuite struct {
+ snapd_testutil.BaseTest
+ authRequestorPlymouthTestMixin
+ authRequestorSystemdTestMixin
+}
+
+func (s *authRequestorAutoSuite) SetUpTest(c *C) {
+ s.AddCleanup(s.authRequestorPlymouthTestMixin.setUpTest(c))
+ s.AddCleanup(s.authRequestorSystemdTestMixin.setUpTest(c))
+}
+
+func (s *authRequestorAutoSuite) setPassphrase(c *C, passphrase string) {
+ s.authRequestorPlymouthTestMixin.setPassphrase(c, passphrase)
+ s.authRequestorSystemdTestMixin.setPassphrase(c, passphrase)
+}
+
+var _ = Suite(&authRequestorAutoSuite{})
+
+type mockAutoAuthRequestorStringer struct {
+ err error
+}
+
+func (s *mockAutoAuthRequestorStringer) RequestUserCredentialString(name, path string, authType UserAuthType) (string, error) {
+ if s.err != nil {
+ return "", s.err
+ }
+
+ var fmtString string
+ switch authType {
+ case UserAuthTypePassphrase:
+ fmtString = "Enter passphrase for %s (%s):"
+ case UserAuthTypePIN:
+ fmtString = "Enter PIN for %s (%s):"
+ case UserAuthTypeRecoveryKey:
+ fmtString = "Enter recovery key for %s (%s):"
+ case UserAuthTypePassphrase | UserAuthTypePIN:
+ fmtString = "Enter passphrase or PIN for %s (%s):"
+ case UserAuthTypePassphrase | UserAuthTypeRecoveryKey:
+ fmtString = "Enter passphrase or recovery key for %s (%s):"
+ case UserAuthTypePIN | UserAuthTypeRecoveryKey:
+ fmtString = "Enter PIN or recovery key for %s (%s):"
+ case UserAuthTypePassphrase | UserAuthTypePIN | UserAuthTypeRecoveryKey:
+ fmtString = "Enter passphrase, PIN or recovery key for %s (%s):"
+ default:
+ return "", errors.New("unexpected UserAuthType")
+ }
+ return fmt.Sprintf(fmtString, name, path), nil
+}
+
+func (s *mockAutoAuthRequestorStringer) NotifyUserAuthResultString(name, path string, result UserAuthResult, authTypes, unavailableAuthTypes UserAuthType) (string, error) {
+ if s.err != nil {
+ return "", s.err
+ }
+
+ switch result {
+ case UserAuthResultSuccess:
+ var fmtString string
+ switch authTypes {
+ case UserAuthTypePassphrase:
+ fmtString = "Unlocked %s (%s) successfully with passphrase"
+ case UserAuthTypePIN:
+ fmtString = "Unlocked %s (%s) successfully with PIN"
+ case UserAuthTypeRecoveryKey:
+ fmtString = "Unlocked %s (%s) successfully with recovery key"
+ default:
+ return "", errors.New("unexpected UserAuthType")
+ }
+ return fmt.Sprintf(fmtString, name, path), nil
+ case UserAuthResultFailed:
+ var b strings.Builder
+
+ switch authTypes {
+ case UserAuthTypePassphrase:
+ io.WriteString(&b, "Incorrect passphrase")
+ case UserAuthTypePIN:
+ io.WriteString(&b, "Incorrect PIN")
+ case UserAuthTypeRecoveryKey:
+ io.WriteString(&b, "Incorrect recovery key")
+ case UserAuthTypePassphrase | UserAuthTypePIN:
+ io.WriteString(&b, "Incorrect passphrase or PIN")
+ case UserAuthTypePassphrase | UserAuthTypeRecoveryKey:
+ io.WriteString(&b, "Incorrect passphrase or recovery key")
+ case UserAuthTypePIN | UserAuthTypeRecoveryKey:
+ io.WriteString(&b, "Incorrect PIN or recovery key")
+ case UserAuthTypePassphrase | UserAuthTypePIN | UserAuthTypeRecoveryKey:
+ io.WriteString(&b, "Incorrect passphrase, PIN or recovery key")
+ default:
+ return "", errors.New("unexpected UserAuthType")
+ }
+
+ switch unavailableAuthTypes {
+ case UserAuthType(0):
+ case UserAuthTypePassphrase:
+ io.WriteString(&b, ". No more passphrase tries remaining")
+ case UserAuthTypePIN:
+ io.WriteString(&b, ". No more PIN tries remaining")
+ case UserAuthTypeRecoveryKey:
+ io.WriteString(&b, ". No more recovery key tries remaining")
+ case UserAuthTypePassphrase | UserAuthTypePIN:
+ io.WriteString(&b, ". No more passphrase or PIN tries remaining")
+ case UserAuthTypePassphrase | UserAuthTypeRecoveryKey:
+ io.WriteString(&b, ". No more passphrase or recovery key tries remaining")
+ case UserAuthTypePIN | UserAuthTypeRecoveryKey:
+ io.WriteString(&b, ". No more PIN or recovery key tries remaining")
+ case UserAuthTypePassphrase | UserAuthTypePIN | UserAuthTypeRecoveryKey:
+ io.WriteString(&b, ". No more passphrase, PIN or recovery key tries remaining")
+ default:
+ return "", errors.New("unexpected UserAuthType")
+ }
+
+ return b.String(), nil
+ case UserAuthResultInvalidFormat:
+ switch authTypes {
+ case UserAuthTypePIN:
+ return "Invalid PIN", nil
+ case UserAuthTypeRecoveryKey:
+ return "Invalid recovery key", nil
+ case UserAuthTypePIN | UserAuthTypeRecoveryKey:
+ return "Invalid PIN or recovery key", nil
+ default:
+ return "", errors.New("unexpected UserAuthType")
+ }
+ default:
+ return "", errors.New("unexpected UserAuthResult")
+ }
+}
+
+func (s *authRequestorAutoSuite) TestNewAuthRequestor(c *C) {
+ sdConsole := new(bytes.Buffer)
+
+ restore := MockNewSystemdAuthRequestor(func(console io.Writer, stringFn SystemdAuthRequestorStringFn) (AuthRequestor, error) {
+ c.Check(console, Equals, sdConsole)
+ return NewSystemdAuthRequestor(console, stringFn)
+ })
+ defer restore()
+
+ requestor, err := NewAutoAuthRequestor(sdConsole, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+ c.Assert(requestor, NotNil)
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Assert(requestor.(*AutoAuthRequestor).Requestors(), HasLen, 2)
+ c.Check(requestor.(*AutoAuthRequestor).Requestors()[0], testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+ c.Check(requestor.(*AutoAuthRequestor).Requestors()[1], testutil.ConvertibleTo, &SystemdAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestNewAuthRequestorPlymouthNotAvailable(c *C) {
+ restore := MockNewPlymouthAuthRequestor(func(_ AuthRequestorStringer) (AuthRequestor, error) {
+ return nil, ErrAuthRequestorNotAvailable
+ })
+ defer restore()
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+ c.Assert(requestor, NotNil)
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Assert(requestor.(*AutoAuthRequestor).Requestors(), HasLen, 1)
+ c.Check(requestor.(*AutoAuthRequestor).Requestors()[0], testutil.ConvertibleTo, &SystemdAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestNewAuthRequestorSystemdNotAvailable(c *C) {
+ restore := MockNewSystemdAuthRequestor(func(_ io.Writer, _ SystemdAuthRequestorStringFn) (AuthRequestor, error) {
+ return nil, ErrAuthRequestorNotAvailable
+ })
+ defer restore()
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+ c.Assert(requestor, NotNil)
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Assert(requestor.(*AutoAuthRequestor).Requestors(), HasLen, 1)
+ c.Check(requestor.(*AutoAuthRequestor).Requestors()[0], testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestNewAuthRequestorNotAvailable(c *C) {
+ restore := MockNewPlymouthAuthRequestor(func(_ AuthRequestorStringer) (AuthRequestor, error) {
+ return nil, ErrAuthRequestorNotAvailable
+ })
+ defer restore()
+
+ restore = MockNewSystemdAuthRequestor(func(_ io.Writer, _ SystemdAuthRequestorStringFn) (AuthRequestor, error) {
+ return nil, ErrAuthRequestorNotAvailable
+ })
+ defer restore()
+
+ _, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Check(err, ErrorMatches, "the auth requestor is not available")
+ c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue)
+}
+
+func (s *authRequestorAutoSuite) TestNewAuthRequestorPlymouthError(c *C) {
+ _, err := NewAutoAuthRequestor(nil, nil)
+ c.Check(err, ErrorMatches, "cannot create Plymouth AuthRequestor: must supply an implementation of AuthRequestorStringer")
+}
+
+func (s *authRequestorAutoSuite) TestNewAuthRequestorSystemdError(c *C) {
+ restore := MockNewSystemdAuthRequestor(func(_ io.Writer, _ SystemdAuthRequestorStringFn) (AuthRequestor, error) {
+ return nil, errors.New("some error")
+ })
+ defer restore()
+
+ _, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Check(err, ErrorMatches, "cannot create systemd AuthRequestor: some error")
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialPlymouth(c *C) {
+ // Ensure that plymouth is used first if available.
+ s.setPassphrase(c, "password")
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, IsNil)
+ c.Check(passphrase, Equals, "password")
+ c.Check(passphraseType, Equals, UserAuthTypePassphrase)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "ask-for-password", "--prompt", "Enter passphrase for data (/dev/sda1):"},
+ })
+ c.Check(s.mockSdAskPassword.Calls(), HasLen, 0)
+
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Check(requestor.(*AutoAuthRequestor).LastUsed(), testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialSystemd(c *C) {
+ // Ensure that systemd-ask-password is used if plymouth isn't running.
+ s.setPassphrase(c, "password")
+ s.stopPlymouthd(c)
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, IsNil)
+ c.Check(passphrase, Equals, "password")
+ c.Check(passphraseType, Equals, UserAuthTypePassphrase)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{{"plymouth", "--ping"}})
+ c.Check(s.mockSdAskPassword.Calls(), DeepEquals, [][]string{{"systemd-ask-password", "--icon", "drive-harddisk", "--id", "secboot.test:/dev/sda1", "Enter passphrase for data (/dev/sda1):"}})
+
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Check(requestor.(*AutoAuthRequestor).LastUsed(), testutil.ConvertibleTo, &SystemdAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentName(c *C) {
+ s.setPassphrase(c, "password")
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "foo", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, IsNil)
+ c.Check(passphrase, Equals, "password")
+ c.Check(passphraseType, Equals, UserAuthTypePassphrase)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "ask-for-password", "--prompt", "Enter passphrase for foo (/dev/sda1):"},
+ })
+ c.Check(s.mockSdAskPassword.Calls(), HasLen, 0)
+
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Check(requestor.(*AutoAuthRequestor).LastUsed(), testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentPath(c *C) {
+ s.setPassphrase(c, "password")
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/nvme0n1p3", UserAuthTypePassphrase)
+ c.Check(err, IsNil)
+ c.Check(passphrase, Equals, "password")
+ c.Check(passphraseType, Equals, UserAuthTypePassphrase)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "ask-for-password", "--prompt", "Enter passphrase for data (/dev/nvme0n1p3):"},
+ })
+ c.Check(s.mockSdAskPassword.Calls(), HasLen, 0)
+
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Check(requestor.(*AutoAuthRequestor).LastUsed(), testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentCredentialType(c *C) {
+ s.setPassphrase(c, "password")
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "foo", "/dev/sda1", UserAuthTypePassphrase|UserAuthTypeRecoveryKey)
+ c.Check(err, IsNil)
+ c.Check(passphrase, Equals, "password")
+ c.Check(passphraseType, Equals, UserAuthTypePassphrase|UserAuthTypeRecoveryKey)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "ask-for-password", "--prompt", "Enter passphrase or recovery key for foo (/dev/sda1):"},
+ })
+ c.Check(s.mockSdAskPassword.Calls(), HasLen, 0)
+
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Check(requestor.(*AutoAuthRequestor).LastUsed(), testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentPassphrase(c *C) {
+ s.setPassphrase(c, "1234")
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, IsNil)
+ c.Check(passphrase, Equals, "1234")
+ c.Check(passphraseType, Equals, UserAuthTypePassphrase)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "ask-for-password", "--prompt", "Enter passphrase for data (/dev/sda1):"},
+ })
+ c.Check(s.mockSdAskPassword.Calls(), HasLen, 0)
+
+ c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{})
+ c.Check(requestor.(*AutoAuthRequestor).LastUsed(), testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialCanceledContext(c *C) {
+ s.setPassphrase(c, "password")
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ _, _, err = requestor.RequestUserCredential(ctx, "data", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, ErrorMatches, `cannot execute plymouth --ping: context canceled`)
+ c.Check(errors.Is(err, context.Canceled), testutil.IsTrue)
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialPlymouthFails(c *C) {
+ // Ensure we get an error if any implementation fails with an unexpected error.
+ s.authRequestorSystemdTestMixin.setPassphrase(c, "password")
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ _, _, err = requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, ErrorMatches, "cannot execute plymouth ask-for-password: exit status 1")
+}
+
+func (s *authRequestorAutoSuite) TestRequestUserCredentialNotAvailable(c *C) {
+ // Ensure we get an appropriate error if no auth requestor is available.
+ s.stopPlymouthd(c)
+
+ restore := MockNewSystemdAuthRequestor(func(_ io.Writer, _ SystemdAuthRequestorStringFn) (AuthRequestor, error) {
+ return nil, ErrAuthRequestorNotAvailable
+ })
+ defer restore()
+
+ requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ _, _, err = requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, ErrorMatches, "the auth requestor is not available")
+ c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue)
+}
+
+func (s *authRequestorAutoSuite) TestNotifyUserAuthResult(c *C) {
+ requestor := NewAutoAuthRequestorForTesting(nil, NewPlymouthAuthRequestorForTesting(new(mockPlymouthAuthRequestorStringer), &PlymouthRequestUserCredentialContext{Name: "data", Path: "/dev/sda1"}))
+
+ c.Check(requestor.NotifyUserAuthResult(context.Background(), UserAuthResultSuccess, UserAuthTypePassphrase, 0), IsNil)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "display-message", "--text", "Unlocked data (/dev/sda1) successfully with passphrase"},
+ })
+}
+
+func (s *authRequestorAutoSuite) TestNotifyUserAuthResultDifferentResult(c *C) {
+ requestor := NewAutoAuthRequestorForTesting(nil, NewPlymouthAuthRequestorForTesting(new(mockPlymouthAuthRequestorStringer), &PlymouthRequestUserCredentialContext{Name: "data", Path: "/dev/sda1"}))
+
+ c.Check(requestor.NotifyUserAuthResult(context.Background(), UserAuthResultFailed, UserAuthTypePassphrase, 0), IsNil)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "display-message", "--text", "Incorrect passphrase"},
+ })
+}
+
+func (s *authRequestorAutoSuite) TestNotifyUserAuthResultDifferentAuthType(c *C) {
+ requestor := NewAutoAuthRequestorForTesting(nil, NewPlymouthAuthRequestorForTesting(new(mockPlymouthAuthRequestorStringer), &PlymouthRequestUserCredentialContext{Name: "data", Path: "/dev/sda1"}))
+
+ c.Check(requestor.NotifyUserAuthResult(context.Background(), UserAuthResultSuccess, UserAuthTypePIN, 0), IsNil)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "display-message", "--text", "Unlocked data (/dev/sda1) successfully with PIN"},
+ })
+}
+
+func (s *authRequestorAutoSuite) TestNotifyUserAuthResultWithExhaustedAuthTypes(c *C) {
+ requestor := NewAutoAuthRequestorForTesting(nil, NewPlymouthAuthRequestorForTesting(new(mockPlymouthAuthRequestorStringer), &PlymouthRequestUserCredentialContext{Name: "data", Path: "/dev/sda1"}))
+
+ c.Check(requestor.NotifyUserAuthResult(context.Background(), UserAuthResultFailed, UserAuthTypePassphrase, UserAuthTypePassphrase), IsNil)
+
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "display-message", "--text", "Incorrect passphrase. No more passphrase tries remaining"},
+ })
+}
+
+func (s *authRequestorAutoSuite) TestNotifyUserAuthResultNoLastUsed(c *C) {
+ requestor := NewAutoAuthRequestorForTesting(nil, nil)
+
+ err := requestor.NotifyUserAuthResult(context.Background(), UserAuthResultSuccess, UserAuthTypePassphrase, 0)
+ c.Check(err, ErrorMatches, `no user credential requested yet`)
+}
+
+func (s *authRequestorAutoSuite) TestNotifyUserAuthResultCanceledContext(c *C) {
+ requestor := NewAutoAuthRequestorForTesting(nil, NewPlymouthAuthRequestorForTesting(new(mockPlymouthAuthRequestorStringer), &PlymouthRequestUserCredentialContext{Name: "data", Path: "/dev/sda1"}))
+
+ ctx, cancel := context.WithCancel(context.Background())
+ cancel()
+
+ err := requestor.NotifyUserAuthResult(ctx, UserAuthResultSuccess, UserAuthTypePassphrase, 0)
+ c.Check(err, ErrorMatches, `cannot execute plymouth --ping: context canceled`)
+ c.Check(errors.Is(err, context.Canceled), testutil.IsTrue)
+}
+
+func (s *authRequestorAutoSuite) TestNotifyUserAuthResultFail(c *C) {
+ requestor := NewAutoAuthRequestorForTesting(nil, NewPlymouthAuthRequestorForTesting(&mockPlymouthAuthRequestorStringer{err: errors.New("some error")}, &PlymouthRequestUserCredentialContext{Name: "data", Path: "/dev/sda1"}))
+
+ err := requestor.NotifyUserAuthResult(context.Background(), UserAuthResultSuccess, UserAuthTypePassphrase, 0)
+ c.Check(err, ErrorMatches, `cannot request message string: some error`)
+}
diff --git a/auth_requestor_plymouth.go b/auth_requestor_plymouth.go
index 33f3134d..592157d6 100644
--- a/auth_requestor_plymouth.go
+++ b/auth_requestor_plymouth.go
@@ -28,30 +28,34 @@ import (
"os/exec"
)
-// PlymouthAuthRequestorStringer is used by the Plymouth implementation
-// of [AuthRequestor] to obtain translated strings.
-type PlymouthAuthRequestorStringer interface {
- // RequestUserCredentialString returns messages used by RequestUserCredential. The
- // name is a string supplied via the WithAuthRequestorUserVisibleName option, and the
- // path is the storage container path.
- RequestUserCredentialString(name, path string, authTypes UserAuthType) (string, error)
-
- // NotifyUserAuthResultString returns messages used by NotifyUserAuthResult.
- NotifyUserAuthResultString(name, path string, result UserAuthResult, authTypes, exhaustedAuthTypes UserAuthType) (string, error)
-}
-
type plymouthRequestUserCredentialContext struct {
Name string
Path string
}
type plymouthAuthRequestor struct {
- stringer PlymouthAuthRequestorStringer
+ stringer AuthRequestorStringer
lastRequestUserCredentialCtx plymouthRequestUserCredentialContext
}
+func (r *plymouthAuthRequestor) ping(ctx context.Context) error {
+ cmd := exec.CommandContext(ctx, "plymouth", "--ping")
+ if err := cmd.Run(); err != nil {
+ var ee *exec.ExitError
+ if errors.As(err, &ee) {
+ return ErrAuthRequestorNotAvailable
+ }
+ return fmt.Errorf("cannot execute plymouth --ping: %w", err)
+ }
+ return nil
+}
+
func (r *plymouthAuthRequestor) RequestUserCredential(ctx context.Context, name, path string, authTypes UserAuthType) (string, UserAuthType, error) {
+ if err := r.ping(ctx); err != nil {
+ return "", 0, err
+ }
+
msg, err := r.stringer.RequestUserCredentialString(name, path, authTypes)
if err != nil {
return "", 0, fmt.Errorf("cannot request message string: %w", err)
@@ -81,6 +85,10 @@ func (r *plymouthAuthRequestor) RequestUserCredential(ctx context.Context, name,
}
func (r *plymouthAuthRequestor) NotifyUserAuthResult(ctx context.Context, result UserAuthResult, authTypes, exhaustedAuthTypes UserAuthType) error {
+ if err := r.ping(ctx); err != nil {
+ return err
+ }
+
msg, err := r.stringer.NotifyUserAuthResultString(r.lastRequestUserCredentialCtx.Name, r.lastRequestUserCredentialCtx.Path, result, authTypes, exhaustedAuthTypes)
if err != nil {
return fmt.Errorf("cannot request message string: %w", err)
@@ -99,9 +107,13 @@ func (r *plymouthAuthRequestor) NotifyUserAuthResult(ctx context.Context, result
// NewPlymouthAuthRequestor creates an implementation of AuthRequestor that
// communicates directly with Plymouth.
-func NewPlymouthAuthRequestor(stringer PlymouthAuthRequestorStringer) (AuthRequestor, error) {
+func NewPlymouthAuthRequestor(stringer AuthRequestorStringer) (AuthRequestor, error) {
+ if _, err := exec.LookPath("plymouth"); err != nil {
+ return nil, ErrAuthRequestorNotAvailable
+ }
+
if stringer == nil {
- return nil, errors.New("must supply an implementation of PlymouthAuthRequestorStringer")
+ return nil, errors.New("must supply an implementation of AuthRequestorStringer")
}
return &plymouthAuthRequestor{
stringer: stringer,
diff --git a/auth_requestor_plymouth_test.go b/auth_requestor_plymouth_test.go
index bd6f1b4c..60b323b9 100644
--- a/auth_requestor_plymouth_test.go
+++ b/auth_requestor_plymouth_test.go
@@ -25,6 +25,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "os"
"path/filepath"
"strings"
@@ -36,26 +37,53 @@ import (
. "github.com/snapcore/secboot"
)
-type authRequestorPlymouthSuite struct {
- snapd_testutil.BaseTest
-
- passwordFile string
- mockPlymouth *snapd_testutil.MockCmd
+type authRequestorPlymouthTestMixin struct {
+ passwordFile string
+ stopPlymouthdFile string
+ displayMessageFailFile string
+ mockPlymouth *snapd_testutil.MockCmd
}
-func (s *authRequestorPlymouthSuite) SetUpTest(c *C) {
+func (m *authRequestorPlymouthTestMixin) setUpTest(c *C) (restore func()) {
dir := c.MkDir()
- s.passwordFile = filepath.Join(dir, "password") // password to be returned by the mock plymouth
-
- plymouthBottom := `if [ "$1" = "ask-for-password" ]; then
-cat %[1]s
+ m.passwordFile = filepath.Join(dir, "password") // password to be returned by the mock plymouth
+ m.stopPlymouthdFile = filepath.Join(dir, "plymouthd-stopped")
+ m.displayMessageFailFile = filepath.Join(dir, "display-message-fail")
+
+ plymouthBottom := `if [ "$1" == "--ping" ] && [ -e %[1]s ]; then
+ exit 1
+elif [ "$1" == "ask-for-password" ]; then
+ cat %[2]s
+elif [ "$1" == "display-message" ] && [ -e %[3]s ]; then
+ exit 1
fi`
- s.mockPlymouth = snapd_testutil.MockCommand(c, "plymouth", fmt.Sprintf(plymouthBottom, s.passwordFile))
- s.AddCleanup(s.mockPlymouth.Restore)
+ m.mockPlymouth = snapd_testutil.MockCommand(c, "plymouth", fmt.Sprintf(plymouthBottom, m.stopPlymouthdFile, m.passwordFile, m.displayMessageFailFile))
+ return m.mockPlymouth.Restore
+}
+
+func (m *authRequestorPlymouthTestMixin) setPassphrase(c *C, passphrase string) {
+ c.Assert(ioutil.WriteFile(m.passwordFile, []byte(passphrase), 0600), IsNil)
+}
+
+func (m *authRequestorPlymouthTestMixin) stopPlymouthd(c *C) {
+ f, err := os.Create(m.stopPlymouthdFile)
+ c.Assert(err, IsNil)
+ f.Close()
}
-func (s *authRequestorPlymouthSuite) setPassphrase(c *C, passphrase string) {
- c.Assert(ioutil.WriteFile(s.passwordFile, []byte(passphrase), 0600), IsNil)
+func (m *authRequestorPlymouthTestMixin) makePlymouthDisplayMessageFail(c *C) {
+ f, err := os.Create(m.displayMessageFailFile)
+ c.Assert(err, IsNil)
+ f.Close()
+}
+
+type authRequestorPlymouthSuite struct {
+ snapd_testutil.BaseTest
+ authRequestorPlymouthTestMixin
+}
+
+func (s *authRequestorPlymouthSuite) SetUpTest(c *C) {
+ s.AddCleanup(s.authRequestorPlymouthTestMixin.setUpTest(c))
}
var _ = Suite(&authRequestorPlymouthSuite{})
@@ -191,8 +219,10 @@ func (s *authRequestorPlymouthSuite) testRequestUserCredential(c *C, params *tes
c.Check(passphrase, Equals, params.passphrase)
c.Check(passphraseType, Equals, params.authTypes)
- c.Check(s.mockPlymouth.Calls(), HasLen, 1)
- c.Check(s.mockPlymouth.Calls()[0], DeepEquals, []string{"plymouth", "ask-for-password", "--prompt", params.expectedMsg})
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "ask-for-password", "--prompt", params.expectedMsg},
+ })
c.Assert(requestor, testutil.ConvertibleTo, &PlymouthAuthRequestor{})
c.Check(requestor.(*PlymouthAuthRequestor).LastRequestUserCredentialCtx(), DeepEquals, PlymouthRequestUserCredentialContext{
@@ -311,9 +341,20 @@ func (s *authRequestorPlymouthSuite) TestRequestUserCredentialPassphraseOrPINOrR
})
}
+func (s *authRequestorPlymouthSuite) TestNewRequestorNotAvailable(c *C) {
+ old := os.Getenv("PATH")
+ dir := c.MkDir()
+ os.Setenv("PATH", dir)
+ defer func() { os.Setenv("PATH", old) }()
+
+ _, err := NewPlymouthAuthRequestor(nil)
+ c.Check(err, ErrorMatches, `the auth requestor is not available`)
+ c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue)
+}
+
func (s *authRequestorPlymouthSuite) TestNewRequestorNoStringer(c *C) {
_, err := NewPlymouthAuthRequestor(nil)
- c.Check(err, ErrorMatches, `must supply an implementation of PlymouthAuthRequestorStringer`)
+ c.Check(err, ErrorMatches, `must supply an implementation of AuthRequestorStringer`)
}
func (s *authRequestorPlymouthSuite) TestRequestUserCredentialObtainMessageError(c *C) {
@@ -348,12 +389,25 @@ func (s *authRequestorPlymouthSuite) TestRequestUserCredentialCanceledContext(c
cancel()
_, _, err = requestor.RequestUserCredential(ctx, "data", "/dev/sda1", UserAuthTypePassphrase)
- c.Check(err, ErrorMatches, "cannot execute plymouth ask-for-password: context canceled")
+ c.Check(err, ErrorMatches, "cannot execute plymouth --ping: context canceled")
c.Check(errors.Is(err, context.Canceled), testutil.IsTrue)
c.Assert(requestor, testutil.ConvertibleTo, &PlymouthAuthRequestor{})
c.Check(requestor.(*PlymouthAuthRequestor).LastRequestUserCredentialCtx(), DeepEquals, PlymouthRequestUserCredentialContext{})
}
+func (s *authRequestorPlymouthSuite) TestRequestUserCredentialNotAvailable(c *C) {
+ requestor, err := NewPlymouthAuthRequestor(new(mockPlymouthAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ s.stopPlymouthd(c)
+
+ _, _, err = requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase)
+ c.Check(err, ErrorMatches, "the auth requestor is not available")
+ c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue)
+ c.Assert(requestor, testutil.ConvertibleTo, &PlymouthAuthRequestor{})
+ c.Check(requestor.(*PlymouthAuthRequestor).LastRequestUserCredentialCtx(), DeepEquals, PlymouthRequestUserCredentialContext{})
+}
+
type testPlymouthNotifyUserAuthResultParams struct {
name string
path string
@@ -369,8 +423,10 @@ func (s *authRequestorPlymouthSuite) testNotifyUserAuthResult(c *C, params *test
c.Check(requestor.NotifyUserAuthResult(context.Background(), params.result, params.authTypes, params.unavailableAuthTypes), IsNil)
- c.Check(s.mockPlymouth.Calls(), HasLen, 1)
- c.Check(s.mockPlymouth.Calls()[0], DeepEquals, []string{"plymouth", "display-message", "--text", params.expectedMsg})
+ c.Check(s.mockPlymouth.Calls(), DeepEquals, [][]string{
+ {"plymouth", "--ping"},
+ {"plymouth", "display-message", "--text", params.expectedMsg},
+ })
}
func (s *authRequestorPlymouthSuite) TestNotifyUserAuthResultSuccessPassphrase(c *C) {
@@ -505,6 +561,17 @@ func (s *authRequestorPlymouthSuite) TestNotifyUserAuthResultInvalidPINOrRecover
})
}
+func (s *authRequestorPlymouthSuite) TestNotifyUserAuthResultNotAvailable(c *C) {
+ requestor, err := NewPlymouthAuthRequestor(new(mockPlymouthAuthRequestorStringer))
+ c.Assert(err, IsNil)
+
+ s.stopPlymouthd(c)
+
+ err = requestor.NotifyUserAuthResult(context.Background(), UserAuthResultSuccess, UserAuthTypePassphrase, 0)
+ c.Check(err, ErrorMatches, "the auth requestor is not available")
+ c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue)
+}
+
func (s *authRequestorPlymouthSuite) TestNotifyUserAuthResultObtainMessageError(c *C) {
requestor, err := NewPlymouthAuthRequestor(&mockPlymouthAuthRequestorStringer{
err: errors.New("some error"),
@@ -522,6 +589,15 @@ func (s *authRequestorPlymouthSuite) TestNotifyUserAuthResultCanceledContext(c *
cancel()
err := requestor.NotifyUserAuthResult(ctx, UserAuthResultSuccess, UserAuthTypePassphrase, 0)
- c.Check(err, ErrorMatches, "cannot execute plymouth display-message: context canceled")
+ c.Check(err, ErrorMatches, "cannot execute plymouth --ping: context canceled")
c.Check(errors.Is(err, context.Canceled), testutil.IsTrue)
}
+
+func (s *authRequestorPlymouthSuite) TestNotifyUserAuthResultFailure(c *C) {
+ requestor := NewPlymouthAuthRequestorForTesting(new(mockPlymouthAuthRequestorStringer), &PlymouthRequestUserCredentialContext{Name: "data", Path: "/dev/sda1"})
+
+ s.makePlymouthDisplayMessageFail(c)
+
+ err := requestor.NotifyUserAuthResult(context.Background(), UserAuthResultSuccess, UserAuthTypePassphrase, 0)
+ c.Check(err, ErrorMatches, "cannot execute plymouth display-message: exit status 1")
+}
diff --git a/auth_requestor_systemd.go b/auth_requestor_systemd.go
index 1e4a9548..a65f7994 100644
--- a/auth_requestor_systemd.go
+++ b/auth_requestor_systemd.go
@@ -93,6 +93,9 @@ func (r *systemdAuthRequestor) NotifyUserAuthResult(ctx context.Context, result
// the implementation of [AuthRequestor.NotifyUserAuthResult] where result is
// not [UserAuthResultSuccess]. If not provided, it defaults to [os.Stderr].
func NewSystemdAuthRequestor(console io.Writer, stringFn SystemdAuthRequestorStringFn) (AuthRequestor, error) {
+ if _, err := exec.LookPath("systemd-ask-password"); err != nil {
+ return nil, ErrAuthRequestorNotAvailable
+ }
if console == nil {
console = os.Stderr
}
diff --git a/auth_requestor_systemd_test.go b/auth_requestor_systemd_test.go
index 16671094..b861d5aa 100644
--- a/auth_requestor_systemd_test.go
+++ b/auth_requestor_systemd_test.go
@@ -36,24 +36,31 @@ import (
. "github.com/snapcore/secboot"
)
-type authRequestorSystemdSuite struct {
- snapd_testutil.BaseTest
-
+type authRequestorSystemdTestMixin struct {
passwordFile string
mockSdAskPassword *snapd_testutil.MockCmd
}
-func (s *authRequestorSystemdSuite) SetUpTest(c *C) {
+func (m *authRequestorSystemdTestMixin) setUpTest(c *C) (restore func()) {
dir := c.MkDir()
- s.passwordFile = filepath.Join(dir, "password") // password to be returned by the mock sd-ask-password
+ m.passwordFile = filepath.Join(dir, "password") // password to be returned by the mock sd-ask-password
sdAskPasswordBottom := `cat %[1]s`
- s.mockSdAskPassword = snapd_testutil.MockCommand(c, "systemd-ask-password", fmt.Sprintf(sdAskPasswordBottom, s.passwordFile))
- s.AddCleanup(s.mockSdAskPassword.Restore)
+ m.mockSdAskPassword = snapd_testutil.MockCommand(c, "systemd-ask-password", fmt.Sprintf(sdAskPasswordBottom, m.passwordFile))
+ return m.mockSdAskPassword.Restore
+}
+
+func (m *authRequestorSystemdTestMixin) setPassphrase(c *C, passphrase string) {
+ c.Assert(ioutil.WriteFile(m.passwordFile, []byte(passphrase+"\n"), 0600), IsNil)
+}
+
+type authRequestorSystemdSuite struct {
+ snapd_testutil.BaseTest
+ authRequestorSystemdTestMixin
}
-func (s *authRequestorSystemdSuite) setPassphrase(c *C, passphrase string) {
- c.Assert(ioutil.WriteFile(s.passwordFile, []byte(passphrase+"\n"), 0600), IsNil)
+func (s *authRequestorSystemdSuite) SetUpTest(c *C) {
+ s.AddCleanup(s.authRequestorSystemdTestMixin.setUpTest(c))
}
var _ = Suite(&authRequestorSystemdSuite{})
@@ -219,6 +226,17 @@ func (s *authRequestorSystemdSuite) TestRequestUserCredentialPassphraseOrPINOrRe
})
}
+func (s *authRequestorSystemdSuite) TestNewRequestorNotAvailable(c *C) {
+ old := os.Getenv("PATH")
+ dir := c.MkDir()
+ os.Setenv("PATH", dir)
+ defer func() { os.Setenv("PATH", old) }()
+
+ _, err := NewSystemdAuthRequestor(nil, nil)
+ c.Check(err, ErrorMatches, `the auth requestor is not available`)
+ c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue)
+}
+
func (s *authRequestorSystemdSuite) TestNewRequestorNoFormatStringCallback(c *C) {
_, err := NewSystemdAuthRequestor(nil, nil)
c.Check(err, ErrorMatches, `must supply a SystemdAuthRequestorStringFn`)
diff --git a/export_test.go b/export_test.go
index 0d5c17d9..377ff677 100644
--- a/export_test.go
+++ b/export_test.go
@@ -74,6 +74,7 @@ type (
ActivateConfigKey = activateConfigKey
ActivateOneContainerStateMachine = activateOneContainerStateMachine
ActivateOneContainerStateMachineFlags = activateOneContainerStateMachineFlags
+ AutoAuthRequestor = autoAuthRequestor
ExternalKeyData = externalKeyData
ExternalUnlockKey = externalUnlockKey
KdfParams = kdfParams
@@ -263,6 +264,22 @@ func MockNewLUKSView(fn func(context.Context, string) (*luksview.View, error)) (
}
}
+func MockNewPlymouthAuthRequestor(fn func(AuthRequestorStringer) (AuthRequestor, error)) (restore func()) {
+ orig := newPlymouthAuthRequestor
+ newPlymouthAuthRequestor = fn
+ return func() {
+ newPlymouthAuthRequestor = orig
+ }
+}
+
+func MockNewSystemdAuthRequestor(fn func(io.Writer, SystemdAuthRequestorStringFn) (AuthRequestor, error)) (restore func()) {
+ orig := newSystemdAuthRequestor
+ newSystemdAuthRequestor = fn
+ return func() {
+ newSystemdAuthRequestor = orig
+ }
+}
+
func MockPBKDF2Benchmark(fn func(time.Duration, crypto.Hash) (uint, error)) (restore func()) {
orig := pbkdf2Benchmark
pbkdf2Benchmark = fn
@@ -311,11 +328,26 @@ func (d *KeyData) DerivePassphraseKeys(passphrase string) (key, iv, auth []byte,
return d.derivePassphraseKeys(passphrase)
}
+func (r *autoAuthRequestor) Requestors() []AuthRequestor {
+ return r.requestors
+}
+
+func (r *autoAuthRequestor) LastUsed() AuthRequestor {
+ return r.lastUsed
+}
+
+func NewAutoAuthRequestorForTesting(requestors []AuthRequestor, lastUsed AuthRequestor) *autoAuthRequestor {
+ return &autoAuthRequestor{
+ requestors: requestors,
+ lastUsed: lastUsed,
+ }
+}
+
func (r *plymouthAuthRequestor) LastRequestUserCredentialCtx() plymouthRequestUserCredentialContext {
return r.lastRequestUserCredentialCtx
}
-func NewPlymouthAuthRequestorForTesting(stringer PlymouthAuthRequestorStringer, lastRequestUserCredentialCtx *plymouthRequestUserCredentialContext) *plymouthAuthRequestor {
+func NewPlymouthAuthRequestorForTesting(stringer AuthRequestorStringer, lastRequestUserCredentialCtx *plymouthRequestUserCredentialContext) *plymouthAuthRequestor {
if lastRequestUserCredentialCtx == nil {
var zeroCtx plymouthRequestUserCredentialContext
lastRequestUserCredentialCtx = &zeroCtx