Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
79 changes: 46 additions & 33 deletions activate.go
Original file line number Diff line number Diff line change
Expand Up @@ -633,17 +633,7 @@ func (m *activateOneContainerStateMachine) tryWithUserAuthKeyslots(ctx context.C
authType |= UserAuthTypeRecoveryKey
}

// XXX: When PIN support lands, this will loop on available PIN tries as well.
for passphraseTries > 0 || pinTries > 0 || recoveryKeyTries > 0 {
// Don't try a method where there are no more usable keyslots.
if !passphraseSlotRecords.hasUsable(m.flags) {
passphraseTries = 0
}
if !pinSlotRecords.hasUsable(m.flags) {
pinTries = 0
}

// Update authType flags
updateAvailableAuthType := func() {
if passphraseTries == 0 {
// No more passphrase key tries are left.
authType &^= UserAuthTypePassphrase
Expand All @@ -656,11 +646,16 @@ func (m *activateOneContainerStateMachine) tryWithUserAuthKeyslots(ctx context.C
// No more recovery key tries are left.
authType &^= UserAuthTypeRecoveryKey
}
}
updateAvailableAuthType()

if authType == UserAuthType(0) {
break
}
var (
triedAuthType UserAuthType // The types of credentials that were tried.
invalidAuthType UserAuthType // The types of credentials that weren't tried because the format is invalid.
successfulAuthType UserAuthType // The type of credential that was used successfully.
)

for authType != UserAuthType(0) {
cred, credAuthType, err := authRequestor.RequestUserCredential(ctx, name, m.container.Path(), authType)
if err != nil {
return fmt.Errorf("cannot request user credential: %w", err)
Expand All @@ -679,8 +674,10 @@ func (m *activateOneContainerStateMachine) tryWithUserAuthKeyslots(ctx context.C
)

if credAuthType&UserAuthTypePassphrase > 0 {
triedAuthType |= UserAuthTypePassphrase
passphraseTries -= 1
if uk, pk, success := m.tryPassphraseKeyslotsHelper(ctx, passphraseSlotRecords, cred); success {
successfulAuthType = UserAuthTypePassphrase
unlockKey = uk
primaryKey = pk
}
Expand All @@ -689,20 +686,15 @@ func (m *activateOneContainerStateMachine) tryWithUserAuthKeyslots(ctx context.C
if m.status == activationIncomplete && credAuthType&UserAuthTypePIN > 0 {
pin, err := ParsePIN(cred)
switch {
case err != nil && authType == UserAuthTypePIN:
// We are only expecting a PIN and the user supplied a badly formatted
// one. We can log this to stderr and allow them another attempt.
// XXX: Maybe display a notice in Plymouth for this case in the
// future.
fmt.Fprintf(m.stderr, "Cannot parse PIN: %v\n", err)
case err != nil:
// The user supplied credential isn't a valid PIN, but it could be
// a valid passphrase or recovery key, so ignore the error in this
// case.
// The user supplied credential isn't a valid PIN.
invalidAuthType |= UserAuthTypePIN
default:
// This is a valid PIN
triedAuthType |= UserAuthTypePIN
pinTries -= 1
if uk, pk, success := m.tryPINKeyslotsHelper(ctx, pinSlotRecords, pin); success {
successfulAuthType = UserAuthTypePIN
unlockKey = uk
primaryKey = pk
}
Expand All @@ -712,32 +704,53 @@ func (m *activateOneContainerStateMachine) tryWithUserAuthKeyslots(ctx context.C
if m.status == activationIncomplete && credAuthType&UserAuthTypeRecoveryKey > 0 {
recoveryKey, err := ParseRecoveryKey(cred)
switch {
case err != nil && authType == UserAuthTypeRecoveryKey:
// We are only expecting a recovery key and the user supplied a badly
// formatted one. We can log this to stderr and allow them another
// attempt.
// XXX: Maybe display a notice in Plymouth for this case in the
// future.
fmt.Fprintf(m.stderr, "Cannot parse recovery key: %v\n", err)
case err != nil:
// The user supplied credential isn't a valid recovery key, but it
// could be a valid PIN or passphrase, so ignore the error in this
// case.
// The user supplied credential isn't a valid recovery key.
invalidAuthType |= UserAuthTypeRecoveryKey
default:
// This is a valid recovery key
triedAuthType |= UserAuthTypeRecoveryKey
recoveryKeyTries -= 1
if m.tryRecoveryKeyslotsHelper(ctx, recoverySlotRecords, recoveryKey) {
successfulAuthType = UserAuthTypeRecoveryKey
unlockKey = DiskUnlockKey(recoveryKey[:])
}
}
}

if m.status == activationIncomplete {
// We haven't unlocked yet, so try again.
// Firstly, don't retry a method where there are no more usable keyslots.
if !passphraseSlotRecords.hasUsable(m.flags) {
passphraseTries = 0
}
if !pinSlotRecords.hasUsable(m.flags) {
pinTries = 0
}

// Update authType flags
prevAuthType := authType
updateAvailableAuthType()
exhaustedAuthType := prevAuthType &^ authType

// Notify the UI
result := UserAuthResultFailed
failedAuthType := triedAuthType
if triedAuthType == UserAuthType(0) && invalidAuthType != UserAuthType(0) {
result = UserAuthResultInvalidFormat
failedAuthType = invalidAuthType
}
if err := authRequestor.NotifyUserAuthResult(ctx, result, failedAuthType, exhaustedAuthType); err != nil {
fmt.Fprintf(m.stderr, "Cannot notify user of auth failure: %v\n", err)
}

continue
}

// We have unlocked successfully.
if err := authRequestor.NotifyUserAuthResult(ctx, UserAuthResultSuccess, successfulAuthType, 0); err != nil {
fmt.Fprintf(m.stderr, "Cannot notify user of auth success: %v\n", err)
}
m.next = activateOneContainerStateMachineTask{
name: "add-keyring-keys",
fn: func(ctx context.Context) error {
Expand Down
120 changes: 104 additions & 16 deletions activate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -412,16 +412,17 @@ type testActivateContextActivateContainerParams struct {

legacyV1KeyUnlock bool

expectedStderr string
expectedTryKeys [][]byte
expectedAuthRequestName string
expectedAuthRequestPath string
expectedAuthRequestTypes []UserAuthType
expectedActivateConfig map[any]any
expectedKeyringKeyPrefix string
expectedPrimaryKey PrimaryKey
expectedUnlockKey DiskUnlockKey
expectedState *ContainerActivateState
expectedStderr string
expectedTryKeys [][]byte
expectedAuthRequestName string
expectedAuthRequestPath string
expectedAuthRequestTypes []UserAuthType
expectedAuthRequestResults []mockAuthRequestorResult
expectedActivateConfig map[any]any
expectedKeyringKeyPrefix string
expectedPrimaryKey PrimaryKey
expectedUnlockKey DiskUnlockKey
expectedState *ContainerActivateState
}

func (s *activateSuite) testActivateContextActivateContainer(c *C, params *testActivateContextActivateContainerParams) error {
Expand Down Expand Up @@ -491,6 +492,39 @@ func (s *activateSuite) testActivateContextActivateContainer(c *C, params *testA
c.Check(req.path, Equals, params.expectedAuthRequestPath)
c.Check(req.authTypes, Equals, params.expectedAuthRequestTypes[i])
}
expectedResults := params.expectedAuthRequestResults
if expectedResults == nil {
var authType UserAuthType
state := expectedState.Activations[params.container.CredentialName()]
switch state.Status {
case ActivationSucceededWithRecoveryKey:
authType = UserAuthTypeRecoveryKey
case ActivationSucceededWithPlatformKey:
data := params.container.slots[state.Keyslot].data
kd, err := ReadKeyData(newMockKeyDataReader("", data))
c.Assert(err, IsNil)
switch kd.AuthMode() {
case AuthModePassphrase:
authType = UserAuthTypePassphrase
case AuthModePIN:
authType = UserAuthTypePIN
}
}
if authType != UserAuthType(0) {
for i := 0; i < len(params.authRequestor.requests)-1; i++ {
expectedResults = append(expectedResults, mockAuthRequestorResult{
result: UserAuthResultFailed,
authTypes: params.expectedAuthRequestTypes[i],
})
}

expectedResults = append(expectedResults, mockAuthRequestorResult{
result: UserAuthResultSuccess,
authTypes: authType,
})
}
}
c.Check(params.authRequestor.results, DeepEquals, expectedResults)
}

expectedCfg := params.expectedActivateConfig
Expand Down Expand Up @@ -1418,14 +1452,16 @@ func (s *activateSuite) TestActivateContainerRecoveryKeyRetryAfterInvalidRecover
opts: []ActivateOption{
WithAuthRequestorUserVisibleName("data"),
},
expectedStderr: `Cannot parse recovery key: incorrectly formatted: insufficient characters
`,
expectedAuthRequestName: "data",
expectedAuthRequestPath: "/dev/sda1",
expectedAuthRequestTypes: []UserAuthType{
UserAuthTypeRecoveryKey,
UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultInvalidFormat, authTypes: UserAuthTypeRecoveryKey},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
AuthRequestorUserVisibleNameKey: "data",
Expand Down Expand Up @@ -1515,8 +1551,6 @@ func (s *activateSuite) TestActivateContainerRecoveryKeyWithRecoveryKeyTries(c *
opts: []ActivateOption{
WithAuthRequestorUserVisibleName("data"),
},
expectedStderr: `Cannot parse recovery key: incorrectly formatted: insufficient characters
`,
expectedTryKeys: [][]byte{incorrectRecoveryKey, incorrectRecoveryKey, incorrectRecoveryKey},
expectedAuthRequestName: "data",
expectedAuthRequestPath: "/dev/sda1",
Expand Down Expand Up @@ -4443,6 +4477,10 @@ func (s *activateSuite) TestActivateContainerAuthModePassphraseWithRecoveryKeyFa
UserAuthTypePassphrase | UserAuthTypeRecoveryKey,
UserAuthTypePassphrase | UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PassphraseTriesKey: uint(3),
Expand Down Expand Up @@ -4517,6 +4555,11 @@ Error with keyslot "default-fallback": cannot recover keys from keyslot: incompa
UserAuthTypePassphrase | UserAuthTypeRecoveryKey,
UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase},
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase, exhaustedAuthTypes: UserAuthTypePassphrase},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PassphraseTriesKey: uint(3),
Expand Down Expand Up @@ -4581,6 +4624,12 @@ func (s *activateSuite) TestActivateContainerAuthModePassphraseWithRecoveryKeyFa
UserAuthTypePassphrase | UserAuthTypeRecoveryKey,
UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase},
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase},
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase, exhaustedAuthTypes: UserAuthTypePassphrase},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PassphraseTriesKey: uint(3),
Expand Down Expand Up @@ -4652,6 +4701,12 @@ func (s *activateSuite) TestActivateContainerAuthModePassphraseAfterRecoveryKeyF
UserAuthTypePassphrase | UserAuthTypeRecoveryKey,
UserAuthTypePassphrase,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase | UserAuthTypeRecoveryKey},
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase | UserAuthTypeRecoveryKey},
{result: UserAuthResultFailed, authTypes: UserAuthTypePassphrase | UserAuthTypeRecoveryKey, exhaustedAuthTypes: UserAuthTypeRecoveryKey},
{result: UserAuthResultSuccess, authTypes: UserAuthTypePassphrase},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PassphraseTriesKey: uint(5),
Expand Down Expand Up @@ -4884,6 +4939,10 @@ func (s *activateSuite) TestActivateContainerAuthModePassphraseAuthRequestorOnly
UserAuthTypePassphrase | UserAuthTypeRecoveryKey,
UserAuthTypePassphrase | UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultInvalidFormat, authTypes: UserAuthTypeRecoveryKey},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PassphraseTriesKey: uint(3),
Expand Down Expand Up @@ -5074,15 +5133,19 @@ func (s *activateSuite) TestActivateContainerAuthModePINWithPINTries(c *C) {
opts: []ActivateOption{
WithAuthRequestorUserVisibleName("data"),
},
expectedStderr: `Cannot parse PIN: invalid PIN: unexpected character
`,
expectedAuthRequestName: "data",
expectedAuthRequestPath: "/dev/sda1",
expectedAuthRequestTypes: []UserAuthType{
UserAuthTypePIN,
UserAuthTypePIN,
UserAuthTypePIN,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN},
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN},
{result: UserAuthResultInvalidFormat, authTypes: UserAuthTypePIN},
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN, exhaustedAuthTypes: UserAuthTypePIN},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PinTriesKey: uint(3),
Expand Down Expand Up @@ -5191,6 +5254,10 @@ func (s *activateSuite) TestActivateContainerAuthModePINWithRecoveryKeyFallback(
UserAuthTypePIN | UserAuthTypeRecoveryKey,
UserAuthTypePIN | UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PinTriesKey: uint(3),
Expand Down Expand Up @@ -5264,6 +5331,11 @@ Error with keyslot "default-fallback": cannot recover keys from keyslot: incompa
UserAuthTypePIN | UserAuthTypeRecoveryKey,
UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN},
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN, exhaustedAuthTypes: UserAuthTypePIN},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PinTriesKey: uint(3),
Expand Down Expand Up @@ -5328,6 +5400,12 @@ func (s *activateSuite) TestActivateContainerAuthModePINWithRecoveryKeyFallbackA
UserAuthTypePIN | UserAuthTypeRecoveryKey,
UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN},
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN},
{result: UserAuthResultFailed, authTypes: UserAuthTypePIN, exhaustedAuthTypes: UserAuthTypePIN},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PinTriesKey: uint(3),
Expand Down Expand Up @@ -5399,6 +5477,12 @@ func (s *activateSuite) TestActivateContainerAuthModePINAfterRecoveryKeyFallback
UserAuthTypePIN | UserAuthTypeRecoveryKey,
UserAuthTypePIN,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultFailed, authTypes: UserAuthTypeRecoveryKey},
{result: UserAuthResultFailed, authTypes: UserAuthTypeRecoveryKey},
{result: UserAuthResultFailed, authTypes: UserAuthTypeRecoveryKey, exhaustedAuthTypes: UserAuthTypeRecoveryKey},
{result: UserAuthResultSuccess, authTypes: UserAuthTypePIN},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PinTriesKey: uint(5),
Expand Down Expand Up @@ -5633,6 +5717,10 @@ func (s *activateSuite) TestActivateContainerAuthModePINAuthRequestorOnlyReturns
UserAuthTypePIN | UserAuthTypeRecoveryKey,
UserAuthTypePIN | UserAuthTypeRecoveryKey,
},
expectedAuthRequestResults: []mockAuthRequestorResult{
{result: UserAuthResultInvalidFormat, authTypes: UserAuthTypeRecoveryKey},
{result: UserAuthResultSuccess, authTypes: UserAuthTypeRecoveryKey},
},
expectedActivateConfig: map[any]any{
AuthRequestorKey: authRequestor,
PinTriesKey: uint(3),
Expand Down
Loading
Loading