From 28d179ef3ca8030a8c45ce0ab40a8b88db7869b8 Mon Sep 17 00:00:00 2001 From: Chris Coulson Date: Fri, 6 Mar 2026 14:04:38 +0000 Subject: [PATCH] Add AuthRequestor that can return credentials from systemd creds This adds an implementation of AuthRequestor that can use systemd credentials as a source for user credentials, useful primarily for snapd spread tests. Credentials are supplied with names following the format .. ... where is "ubuntu-fde" by default, is the path with the leading separator removed and remaining separators replaced with '-', and is one of "passphrase", "pin" or "recoverykey". Fixes: FR-12438 --- auth_requestor_auto.go | 21 ++- auth_requestor_auto_test.go | 82 +++++++-- auth_requestor_plymouth_test.go | 2 + auth_requestor_sdcreds.go | 124 +++++++++++++ auth_requestor_sdcreds_test.go | 312 ++++++++++++++++++++++++++++++++ auth_requestor_systemd_test.go | 2 + export_test.go | 47 +++-- 7 files changed, 554 insertions(+), 36 deletions(-) create mode 100644 auth_requestor_sdcreds.go create mode 100644 auth_requestor_sdcreds_test.go diff --git a/auth_requestor_auto.go b/auth_requestor_auto.go index 7f41a3d2..6179d9d4 100644 --- a/auth_requestor_auto.go +++ b/auth_requestor_auto.go @@ -27,8 +27,9 @@ import ( ) var ( - newPlymouthAuthRequestor = NewPlymouthAuthRequestor - newSystemdAuthRequestor = NewSystemdAuthRequestor + newPlymouthAuthRequestor = NewPlymouthAuthRequestor + newSystemdAuthRequestor = NewSystemdAuthRequestor + newSystemdCredsAuthRequestor = NewSystemdCredsAuthRequestor ) type autoAuthRequestor struct { @@ -59,6 +60,7 @@ func (r *autoAuthRequestor) NotifyUserAuthResult(ctx context.Context, result Use // NewAutoAuthRequestor creates an implementation of AuthRequestor that automatically // selects the first available implementation in the following order: +// - systemd credential. // - Plymouth. // - systemd-ask-password. // @@ -71,9 +73,20 @@ func (r *autoAuthRequestor) NotifyUserAuthResult(ctx context.Context, result Use // 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) { +// provided, it defaults to [os.Stderr]. The credPrefix argument is used to specify the +// prefix for systemd credentials. +func NewAutoAuthRequestor(stderr io.Writer, stringer AuthRequestorStringer, credPrefix string) (AuthRequestor, error) { var requestors []AuthRequestor + + switch sdcred, err := newSystemdCredsAuthRequestor(stderr, credPrefix); { + case errors.Is(err, ErrAuthRequestorNotAvailable): + // ignore + case err != nil: + return nil, fmt.Errorf("cannot create sdcreds AuthRequestor: %w", err) + default: + requestors = append(requestors, sdcred) + } + switch ply, err := newPlymouthAuthRequestor(stringer); { case errors.Is(err, ErrAuthRequestorNotAvailable): // ignore diff --git a/auth_requestor_auto_test.go b/auth_requestor_auto_test.go index c7fbd22e..a5b6bbf7 100644 --- a/auth_requestor_auto_test.go +++ b/auth_requestor_auto_test.go @@ -25,6 +25,7 @@ import ( "errors" "fmt" "io" + "os" "strings" . "github.com/snapcore/secboot" @@ -36,11 +37,13 @@ import ( type authRequestorAutoSuite struct { snapd_testutil.BaseTest + authRequestorSdCredsTestMixin authRequestorPlymouthTestMixin authRequestorSystemdTestMixin } func (s *authRequestorAutoSuite) SetUpTest(c *C) { + s.AddCleanup(s.authRequestorSdCredsTestMixin.setUpTest(c)) s.AddCleanup(s.authRequestorPlymouthTestMixin.setUpTest(c)) s.AddCleanup(s.authRequestorSystemdTestMixin.setUpTest(c)) } @@ -170,7 +173,22 @@ func (s *authRequestorAutoSuite) TestNewAuthRequestor(c *C) { }) defer restore() - requestor, err := NewAutoAuthRequestor(sdConsole, new(mockAutoAuthRequestorStringer)) + 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, 3) + c.Check(requestor.(*AutoAuthRequestor).Requestors()[0], testutil.ConvertibleTo, &SystemdCredsAuthRequestor{}) + c.Check(requestor.(*AutoAuthRequestor).Requestors()[1], testutil.ConvertibleTo, &PlymouthAuthRequestor{}) + c.Check(requestor.(*AutoAuthRequestor).Requestors()[2], testutil.ConvertibleTo, &SystemdAuthRequestor{}) +} + +func (s *authRequestorAutoSuite) TestNewAuthRequestorSdCredsNotAvailable(c *C) { + sdConsole := new(bytes.Buffer) + + c.Assert(os.Unsetenv("CREDENTIALS_DIRECTORY"), IsNil) + + requestor, err := NewAutoAuthRequestor(sdConsole, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) c.Assert(requestor, NotNil) c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{}) @@ -185,12 +203,13 @@ func (s *authRequestorAutoSuite) TestNewAuthRequestorPlymouthNotAvailable(c *C) }) defer restore() - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + 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{}) + c.Assert(requestor.(*AutoAuthRequestor).Requestors(), HasLen, 2) + c.Check(requestor.(*AutoAuthRequestor).Requestors()[0], testutil.ConvertibleTo, &SystemdCredsAuthRequestor{}) + c.Check(requestor.(*AutoAuthRequestor).Requestors()[1], testutil.ConvertibleTo, &SystemdAuthRequestor{}) } func (s *authRequestorAutoSuite) TestNewAuthRequestorSystemdNotAvailable(c *C) { @@ -199,15 +218,18 @@ func (s *authRequestorAutoSuite) TestNewAuthRequestorSystemdNotAvailable(c *C) { }) defer restore() - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + 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{}) + c.Assert(requestor.(*AutoAuthRequestor).Requestors(), HasLen, 2) + c.Check(requestor.(*AutoAuthRequestor).Requestors()[0], testutil.ConvertibleTo, &SystemdCredsAuthRequestor{}) + c.Check(requestor.(*AutoAuthRequestor).Requestors()[1], testutil.ConvertibleTo, &PlymouthAuthRequestor{}) } func (s *authRequestorAutoSuite) TestNewAuthRequestorNotAvailable(c *C) { + c.Assert(os.Unsetenv("CREDENTIALS_DIRECTORY"), IsNil) + restore := MockNewPlymouthAuthRequestor(func(_ AuthRequestorStringer) (AuthRequestor, error) { return nil, ErrAuthRequestorNotAvailable }) @@ -218,13 +240,13 @@ func (s *authRequestorAutoSuite) TestNewAuthRequestorNotAvailable(c *C) { }) defer restore() - _, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + _, 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) + _, err := NewAutoAuthRequestor(nil, nil, "") c.Check(err, ErrorMatches, "cannot create Plymouth AuthRequestor: must supply an implementation of AuthRequestorStringer") } @@ -234,15 +256,35 @@ func (s *authRequestorAutoSuite) TestNewAuthRequestorSystemdError(c *C) { }) defer restore() - _, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + _, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Check(err, ErrorMatches, "cannot create systemd AuthRequestor: some error") } +func (s *authRequestorAutoSuite) TestRequestUserCredentialSdCred(c *C) { + // Ensure that a systemd cred is used first if available. + s.setPassphrase(c, "password") + s.writeCred(c, "foo", "/dev/sda1", "password", "passphrase") + + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "foo") + 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(), HasLen, 0) + c.Check(s.mockSdAskPassword.Calls(), HasLen, 0) + + c.Assert(requestor, testutil.ConvertibleTo, &AutoAuthRequestor{}) + c.Check(requestor.(*AutoAuthRequestor).LastUsed(), testutil.ConvertibleTo, &SystemdCredsAuthRequestor{}) +} + func (s *authRequestorAutoSuite) TestRequestUserCredentialPlymouth(c *C) { - // Ensure that plymouth is used first if available. + // Ensure that plymouth is used first if available and there is no systemd cred. s.setPassphrase(c, "password") - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase) @@ -265,7 +307,7 @@ func (s *authRequestorAutoSuite) TestRequestUserCredentialSystemd(c *C) { s.setPassphrase(c, "password") s.stopPlymouthd(c) - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase) @@ -283,7 +325,7 @@ func (s *authRequestorAutoSuite) TestRequestUserCredentialSystemd(c *C) { func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentName(c *C) { s.setPassphrase(c, "password") - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "foo", "/dev/sda1", UserAuthTypePassphrase) @@ -304,7 +346,7 @@ func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentName(c *C) { func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentPath(c *C) { s.setPassphrase(c, "password") - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/nvme0n1p3", UserAuthTypePassphrase) @@ -325,7 +367,7 @@ func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentPath(c *C) { func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentCredentialType(c *C) { s.setPassphrase(c, "password") - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "foo", "/dev/sda1", UserAuthTypePassphrase|UserAuthTypeRecoveryKey) @@ -346,7 +388,7 @@ func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentCredentialTyp func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentPassphrase(c *C) { s.setPassphrase(c, "1234") - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase) @@ -367,7 +409,7 @@ func (s *authRequestorAutoSuite) TestRequestUserCredentialDifferentPassphrase(c func (s *authRequestorAutoSuite) TestRequestUserCredentialCanceledContext(c *C) { s.setPassphrase(c, "password") - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) ctx, cancel := context.WithCancel(context.Background()) @@ -382,7 +424,7 @@ 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)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) _, _, err = requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase) @@ -398,7 +440,7 @@ func (s *authRequestorAutoSuite) TestRequestUserCredentialNotAvailable(c *C) { }) defer restore() - requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer)) + requestor, err := NewAutoAuthRequestor(nil, new(mockAutoAuthRequestorStringer), "") c.Assert(err, IsNil) _, _, err = requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePassphrase) diff --git a/auth_requestor_plymouth_test.go b/auth_requestor_plymouth_test.go index 60b323b9..95108542 100644 --- a/auth_requestor_plymouth_test.go +++ b/auth_requestor_plymouth_test.go @@ -83,6 +83,8 @@ type authRequestorPlymouthSuite struct { } func (s *authRequestorPlymouthSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + s.AddCleanup(s.authRequestorPlymouthTestMixin.setUpTest(c)) } diff --git a/auth_requestor_sdcreds.go b/auth_requestor_sdcreds.go new file mode 100644 index 00000000..a056ccb6 --- /dev/null +++ b/auth_requestor_sdcreds.go @@ -0,0 +1,124 @@ +// -*- 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" + "os" + "path/filepath" + "strings" +) + +type systemdCredsRequestUserCredentialContext struct { + Path string + CredPath string +} + +type systemdCredsAuthRequestor struct { + console io.Writer + prefix string + credsDir string + + lastRequestUserCredentialCtx systemdCredsRequestUserCredentialContext +} + +func (r *systemdCredsAuthRequestor) RequestUserCredential(ctx context.Context, name, path string, authTypes UserAuthType) (string, UserAuthType, error) { + pathStr := strings.ReplaceAll(strings.TrimPrefix(path, "/"), "/", "-") + + for _, info := range []struct { + authTypeStr string + authType UserAuthType + }{ + {authTypeStr: "passphrase", authType: UserAuthTypePassphrase}, + {authTypeStr: "pin", authType: UserAuthTypePIN}, + {authTypeStr: "recoverykey", authType: UserAuthTypeRecoveryKey}, + } { + if info.authType&authTypes == 0 { + continue + } + + credPath := filepath.Join(r.credsDir, fmt.Sprintf("%s.%s.%s", r.prefix, pathStr, info.authTypeStr)) + data, err := os.ReadFile(credPath) + if errors.Is(err, os.ErrNotExist) { + continue + } + if err != nil { + return "", 0, fmt.Errorf("cannot read credential from %s: %w", path, err) + } + + r.lastRequestUserCredentialCtx = systemdCredsRequestUserCredentialContext{ + Path: path, + CredPath: credPath, + } + return string(data), info.authType, nil + } + + return "", 0, ErrAuthRequestorNotAvailable +} + +func (r *systemdCredsAuthRequestor) NotifyUserAuthResult(ctx context.Context, result UserAuthResult, authTypes, exhaustedAuthTypes UserAuthType) error { + switch result { + case UserAuthResultFailed: + fmt.Fprintf(r.console, "Incorrect %s from credential %s for %s\n", formatUserAuthTypeString(authTypes), r.lastRequestUserCredentialCtx.CredPath, r.lastRequestUserCredentialCtx.Path) + case UserAuthResultInvalidFormat: + fmt.Fprintf(r.console, "Incorrectly formatted %s from credential %s\n", formatUserAuthTypeString(authTypes), r.lastRequestUserCredentialCtx.CredPath) + } + + r.lastRequestUserCredentialCtx = systemdCredsRequestUserCredentialContext{} + return nil +} + +// NewSystemdCredsAuthRequestor creates an implementation of AuthRequestor that +// returns credentials from systemd credentials. The console argument is used by +// the implementation of [AuthRequestor.NotifyUserAuthResult] where result is +// not [UserAuthResultSuccess]. If not provided, it defaults to [os.Stderr]. +// The prefix argument can be used to customize the prefix used for looking up +// credentials. It defaults to "ubuntu-fde" if not provided. +// +// Credentials can be provided by using the following format for their name: +// .. +// ... where is the path with the leading separator removed and remaining +// separators replaced with '-', and is one of "passphrase", "pin" or +// "recoverykey". +// +// This will return [ErrAuthRequestorNotAvailable] if the CREDENTIALS_DIRECTORY +// environment variable is not set. +func NewSystemdCredsAuthRequestor(console io.Writer, prefix string) (AuthRequestor, error) { + dir, exists := os.LookupEnv("CREDENTIALS_DIRECTORY") + if !exists { + return nil, ErrAuthRequestorNotAvailable + } + + if console == nil { + console = os.Stderr + } + if prefix == "" { + prefix = "ubuntu-fde" + } + + return &systemdCredsAuthRequestor{ + console: console, + prefix: prefix, + credsDir: dir, + }, nil +} diff --git a/auth_requestor_sdcreds_test.go b/auth_requestor_sdcreds_test.go new file mode 100644 index 00000000..aab2d50d --- /dev/null +++ b/auth_requestor_sdcreds_test.go @@ -0,0 +1,312 @@ +// -*- 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" + "os" + "path/filepath" + "strings" + + . "gopkg.in/check.v1" + + . "github.com/snapcore/secboot" + "github.com/snapcore/secboot/internal/testutil" + snapd_testutil "github.com/snapcore/snapd/testutil" +) + +type authRequestorSdCredsTestMixin struct { + credsDir string +} + +func (m *authRequestorSdCredsTestMixin) setUpTest(c *C) (restore func()) { + m.credsDir = c.MkDir() + + name := "CREDENTIALS_DIRECTORY" + orig, set := os.LookupEnv(name) + c.Assert(os.Setenv(name, m.credsDir), IsNil) + return func() { + if !set { + return + } + c.Assert(os.Setenv(name, orig), IsNil) + } +} + +func (m *authRequestorSdCredsTestMixin) writeCred(c *C, prefix, path, cred, credType string) string { + name := filepath.Join(m.credsDir, fmt.Sprintf("%s.%s.%s", prefix, strings.ReplaceAll(path[1:], "/", "-"), credType)) + c.Assert(os.WriteFile(name, []byte(cred), 0644), IsNil) + return name +} + +type authRequestorSdCredsSuite struct { + snapd_testutil.BaseTest + authRequestorSdCredsTestMixin +} + +func (s *authRequestorSdCredsSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + + s.AddCleanup(s.authRequestorSdCredsTestMixin.setUpTest(c)) +} + +var _ = Suite(&authRequestorSdCredsSuite{}) + +type testSdCredsRequestUserCredentialParams struct { + passphrase string + passphraseType string + + prefix string + + path string + authTypes UserAuthType + + expectedAuthType UserAuthType +} + +func (s *authRequestorSdCredsSuite) testRequestUserCredential(c *C, params *testSdCredsRequestUserCredentialParams) { + prefix := params.prefix + if prefix == "" { + prefix = "ubuntu-fde" + } + credPath := s.writeCred(c, prefix, params.path, params.passphrase, params.passphraseType) + + requestor, err := NewSystemdCredsAuthRequestor(nil, params.prefix) + c.Assert(err, IsNil) + + passphrase, passphraseType, err := requestor.RequestUserCredential(context.Background(), "data", params.path, params.authTypes) + c.Check(err, IsNil) + c.Check(passphrase, Equals, params.passphrase) + c.Check(passphraseType, Equals, params.expectedAuthType) + + c.Assert(requestor, testutil.ConvertibleTo, &SystemdCredsAuthRequestor{}) + c.Check(requestor.(*SystemdCredsAuthRequestor).LastRequestUserCredentialCtx(), DeepEquals, SystemdCredsRequestUserCredentialContext{ + Path: params.path, + CredPath: credPath, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredential(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "password", + passphraseType: "passphrase", + path: "/dev/sda1", + authTypes: UserAuthTypePassphrase, + expectedAuthType: UserAuthTypePassphrase, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialDifferentPassphrase(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "1234", + passphraseType: "passphrase", + path: "/dev/sda1", + authTypes: UserAuthTypePassphrase, + expectedAuthType: UserAuthTypePassphrase, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialDifferentPath(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "password", + passphraseType: "passphrase", + path: "/dev/nvme0n1p3", + authTypes: UserAuthTypePassphrase, + expectedAuthType: UserAuthTypePassphrase, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialPIN(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "1234", + passphraseType: "pin", + path: "/dev/sda1", + authTypes: UserAuthTypePIN, + expectedAuthType: UserAuthTypePIN, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialRecoveryKey(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "00000-11111-22222-33333-44444-55555-00000-11111", + passphraseType: "recoverykey", + path: "/dev/sda1", + authTypes: UserAuthTypeRecoveryKey, + expectedAuthType: UserAuthTypeRecoveryKey, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialPassphraseOrPIN(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "1234", + passphraseType: "pin", + path: "/dev/sda1", + authTypes: UserAuthTypePassphrase | UserAuthTypePIN, + expectedAuthType: UserAuthTypePIN, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialPassphraseOrRecoveryKey(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "password", + passphraseType: "passphrase", + path: "/dev/sda1", + authTypes: UserAuthTypePassphrase | UserAuthTypeRecoveryKey, + expectedAuthType: UserAuthTypePassphrase, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialPINOrRecoveryKey(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "1234", + passphraseType: "pin", + path: "/dev/sda1", + authTypes: UserAuthTypePIN | UserAuthTypeRecoveryKey, + expectedAuthType: UserAuthTypePIN, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialPassphraseOrPINOrRecoveryKey(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "password", + passphraseType: "passphrase", + path: "/dev/sda1", + authTypes: UserAuthTypePassphrase | UserAuthTypePIN | UserAuthTypeRecoveryKey, + expectedAuthType: UserAuthTypePassphrase, + }) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialDifferentPrefix(c *C) { + s.testRequestUserCredential(c, &testSdCredsRequestUserCredentialParams{ + passphrase: "password", + passphraseType: "passphrase", + prefix: "foo", + path: "/dev/sda1", + authTypes: UserAuthTypePassphrase, + expectedAuthType: UserAuthTypePassphrase, + }) +} + +func (s *authRequestorSdCredsSuite) TestNewRequestorNotAvailable(c *C) { + c.Assert(os.Unsetenv("CREDENTIALS_DIRECTORY"), IsNil) + + _, err := NewSystemdCredsAuthRequestor(nil, "") + c.Check(err, ErrorMatches, `the auth requestor is not available`) + c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue) +} + +func (s *authRequestorSdCredsSuite) TestRequestUserCredentialNotAvailable(c *C) { + s.writeCred(c, "ubuntu-fde", "/dev/sda1", "passphrase", "passphrase") + + requestor, err := NewSystemdCredsAuthRequestor(nil, "") + c.Assert(err, IsNil) + + _, _, err = requestor.RequestUserCredential(context.Background(), "data", "/dev/sda1", UserAuthTypePIN|UserAuthTypeRecoveryKey) + c.Check(err, ErrorMatches, `the auth requestor is not available`) + c.Check(errors.Is(err, ErrAuthRequestorNotAvailable), testutil.IsTrue) + + c.Assert(requestor, testutil.ConvertibleTo, &SystemdCredsAuthRequestor{}) + c.Check(requestor.(*SystemdCredsAuthRequestor).LastRequestUserCredentialCtx(), DeepEquals, SystemdCredsRequestUserCredentialContext{}) +} + +type testSdCredsNotifyUserAuthResultParams struct { + path string + credPath string + result UserAuthResult + authTypes UserAuthType + unavailableAuthTypes UserAuthType + + expectedMsg string +} + +func (s *authRequestorSdCredsSuite) testNotifyUserAuthResult(c *C, params *testSdCredsNotifyUserAuthResultParams) { + console := new(bytes.Buffer) + requestor := NewSystemdCredsAuthRequestorForTesting(console, "", "", &SystemdCredsRequestUserCredentialContext{ + CredPath: params.credPath, + Path: params.path, + }) + + c.Check(requestor.NotifyUserAuthResult(nil, params.result, params.authTypes, params.unavailableAuthTypes), IsNil) + c.Check(console.String(), Equals, params.expectedMsg) +} + +func (s *authRequestorSdCredsSuite) TestNotifyUserAuthResultSuccess(c *C) { + s.testNotifyUserAuthResult(c, &testSdCredsNotifyUserAuthResultParams{ + result: UserAuthResultSuccess, + }) +} + +func (s *authRequestorSdCredsSuite) TestNotifyUserAuthResultFailurePassphrase(c *C) { + s.testNotifyUserAuthResult(c, &testSdCredsNotifyUserAuthResultParams{ + path: "/dev/sda1", + credPath: "/foo/ubuntu-fde.dev-sda1.passphrase", + result: UserAuthResultFailed, + authTypes: UserAuthTypePassphrase, + expectedMsg: `Incorrect passphrase from credential /foo/ubuntu-fde.dev-sda1.passphrase for /dev/sda1 +`, + }) +} + +func (s *authRequestorSdCredsSuite) TestNotifyUserAuthResultFailurePIN(c *C) { + s.testNotifyUserAuthResult(c, &testSdCredsNotifyUserAuthResultParams{ + path: "/dev/sda1", + credPath: "/foo/ubuntu-fde.dev-sda1.pin", + result: UserAuthResultFailed, + authTypes: UserAuthTypePIN, + expectedMsg: `Incorrect PIN from credential /foo/ubuntu-fde.dev-sda1.pin for /dev/sda1 +`, + }) +} + +func (s *authRequestorSdCredsSuite) TestNotifyUserAuthResultFailureDifferentPath(c *C) { + s.testNotifyUserAuthResult(c, &testSdCredsNotifyUserAuthResultParams{ + path: "/dev/nvme0n1p3", + credPath: "/foo/ubuntu-fde.dev-nvme0n1p3.passphrase", + result: UserAuthResultFailed, + authTypes: UserAuthTypePassphrase, + expectedMsg: `Incorrect passphrase from credential /foo/ubuntu-fde.dev-nvme0n1p3.passphrase for /dev/nvme0n1p3 +`, + }) +} + +func (s *authRequestorSdCredsSuite) TestNotifyUserAuthResultInvalidPIN(c *C) { + s.testNotifyUserAuthResult(c, &testSdCredsNotifyUserAuthResultParams{ + path: "/dev/sda1", + credPath: "/foo/ubuntu-fde.dev-sda1.pin", + result: UserAuthResultInvalidFormat, + authTypes: UserAuthTypePIN, + expectedMsg: `Incorrectly formatted PIN from credential /foo/ubuntu-fde.dev-sda1.pin +`, + }) +} + +func (s *authRequestorSdCredsSuite) TestNotifyUserAuthResultInvalidRecoveryKey(c *C) { + s.testNotifyUserAuthResult(c, &testSdCredsNotifyUserAuthResultParams{ + path: "/dev/sda1", + credPath: "/foo/ubuntu-fde.dev-sda1.recoverykey", + result: UserAuthResultInvalidFormat, + authTypes: UserAuthTypeRecoveryKey, + expectedMsg: `Incorrectly formatted recovery key from credential /foo/ubuntu-fde.dev-sda1.recoverykey +`, + }) +} diff --git a/auth_requestor_systemd_test.go b/auth_requestor_systemd_test.go index b861d5aa..b7158bd9 100644 --- a/auth_requestor_systemd_test.go +++ b/auth_requestor_systemd_test.go @@ -60,6 +60,8 @@ type authRequestorSystemdSuite struct { } func (s *authRequestorSystemdSuite) SetUpTest(c *C) { + s.BaseTest.SetUpTest(c) + s.AddCleanup(s.authRequestorSystemdTestMixin.setUpTest(c)) } diff --git a/export_test.go b/export_test.go index 377ff677..9d8634ef 100644 --- a/export_test.go +++ b/export_test.go @@ -70,18 +70,20 @@ var ( ) type ( - ActivateConfigImpl = activateConfig - ActivateConfigKey = activateConfigKey - ActivateOneContainerStateMachine = activateOneContainerStateMachine - ActivateOneContainerStateMachineFlags = activateOneContainerStateMachineFlags - AutoAuthRequestor = autoAuthRequestor - ExternalKeyData = externalKeyData - ExternalUnlockKey = externalUnlockKey - KdfParams = kdfParams - PlymouthAuthRequestor = plymouthAuthRequestor - PlymouthRequestUserCredentialContext = plymouthRequestUserCredentialContext - ProtectedKeys = protectedKeys - SystemdAuthRequestor = systemdAuthRequestor + ActivateConfigImpl = activateConfig + ActivateConfigKey = activateConfigKey + ActivateOneContainerStateMachine = activateOneContainerStateMachine + ActivateOneContainerStateMachineFlags = activateOneContainerStateMachineFlags + AutoAuthRequestor = autoAuthRequestor + ExternalKeyData = externalKeyData + ExternalUnlockKey = externalUnlockKey + KdfParams = kdfParams + PlymouthAuthRequestor = plymouthAuthRequestor + PlymouthRequestUserCredentialContext = plymouthRequestUserCredentialContext + ProtectedKeys = protectedKeys + SystemdAuthRequestor = systemdAuthRequestor + SystemdCredsAuthRequestor = systemdCredsAuthRequestor + SystemdCredsRequestUserCredentialContext = systemdCredsRequestUserCredentialContext ) func KDFOptionsKdfParams(opts KDFOptions, defaultTargetDuration time.Duration, keyLen uint32) (*KdfParams, error) { @@ -280,6 +282,14 @@ func MockNewSystemdAuthRequestor(fn func(io.Writer, SystemdAuthRequestorStringFn } } +func MockNewSystemdCredsAuthRequestor(fn func(io.Writer, string) (AuthRequestor, error)) (restore func()) { + orig := newSystemdCredsAuthRequestor + newSystemdCredsAuthRequestor = fn + return func() { + newSystemdCredsAuthRequestor = orig + } +} + func MockPBKDF2Benchmark(fn func(time.Duration, crypto.Hash) (uint, error)) (restore func()) { orig := pbkdf2Benchmark pbkdf2Benchmark = fn @@ -370,6 +380,19 @@ func NewSystemdAuthRequestorForTesting(console io.Writer, stringFn SystemdAuthRe } } +func (r *systemdCredsAuthRequestor) LastRequestUserCredentialCtx() systemdCredsRequestUserCredentialContext { + return r.lastRequestUserCredentialCtx +} + +func NewSystemdCredsAuthRequestorForTesting(console io.Writer, prefix, credsDir string, lastRequestUserCredentialCtx *systemdCredsRequestUserCredentialContext) *systemdCredsAuthRequestor { + return &systemdCredsAuthRequestor{ + console: console, + prefix: prefix, + credsDir: credsDir, + lastRequestUserCredentialCtx: *lastRequestUserCredentialCtx, + } +} + func MockUnixStat(f func(devicePath string, st *unix.Stat_t) error) (restore func()) { old := unixStat unixStat = f