Files
snapd/secboot/secboot.go
Michael Vogt caf7a137ae boot,secboot: reset DA lockout counter after successful boot
* gadget/device: introduce package which provides helpers for locations of things

Various helpers for getting location of things were spread out or doubled in the
code base. This package aggregates them in one place.

Signed-off-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>

* boot,secboot: reset DA lockout counter after successful boot

The TPM DictionaryAttack lockout counter is incremented after each
unclean shutdown according to Chris Coulson in LP:1979185.

This means that eventually enough unclean shutdowns the system
fails to boot and asks for the recovery key. To fix this snapd
needs to clean the DA lockout counter after each successful
run.

* device: add missing doc string for TpmLockoutAuthUnder

* secboot: add missing tests around MarkSuccessful

* tests: add nested core20-da-lockout test

This commits adds a nested core20 test that triggers a bunch of
unclean shutdowns in a nested VM. On a system that does not have
the new `secboot.MarkSuccessful()` code this will eventually
trigger a recovery key prompt as each unclean reboot increases
the DA lockout counter by one.
```
google-nested:ubuntu-20.04-64 /tmp/work-dir/logs# tail -n1 /tmp/work-dir/logs/serial.log
Please enter the recovery key for disk /dev/disk/by-partuuid/3a1bacae-5d46-ce4b-960a-4074d18a8c05: (press TAB for no echo)
```

* devicestate: fix go fmt error

* tests: set nested/manual/core20-da-lockout to manual and improve comment/description

* secboot: improve docstrings

* secboot: shuffle code around so that tests with `-tags nosecboot` work correctly

* boot,device: extract StampSealedKeys,SealedKeysMethod from boot to device

* secboot: use new device.SealedKeysMethod()

* tests: make core20-da-lockout faster by reading the da lockout counter directly (thanks to Chris Coulson)

* tests: add new 'test.nested vm unclean-reboot` and use in da lockout test

* tests: retry on lockout counter reading

The lockout counter is cleared during snapd startup so we need
to retry for a bit to see it cleared.

* tests: fix whitespace

* secboot: improve test around testMarkSuccessfulEncrypted and also check authValue

* secboot: fix silly typo (thanks to Samuele)

Co-authored-by: Maciej Borzecki <maciej.zenon.borzecki@canonical.com>
2022-07-12 18:38:05 +02:00

224 lines
7.4 KiB
Go

// -*- Mode: Go; indent-tabs-mode: t -*-
/*
* Copyright (C) 2021 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
// This file must not have a build-constraint and must not import
// the github.com/snapcore/secboot repository. That will ensure
// it can be build as part of the debian build without secboot.
// Debian does run "go list" without any support for passing -tags.
import (
"crypto/ecdsa"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/dirs"
"github.com/snapcore/snapd/gadget/device"
"github.com/snapcore/snapd/secboot/keys"
)
const (
// Handles are in the block reserved for TPM owner objects (0x01800000 - 0x01bfffff).
//
// Handles are rotated during factory reset, depending on the PCR handle
// thet was used when sealing key objects during installation (or a
// previous factory reset).
RunObjectPCRPolicyCounterHandle = uint32(0x01880001)
FallbackObjectPCRPolicyCounterHandle = uint32(0x01880002)
AltRunObjectPCRPolicyCounterHandle = uint32(0x01880003)
AltFallbackObjectPCRPolicyCounterHandle = uint32(0x01880004)
)
// WithSecbootSupport is true if this package was built with githbu.com/snapcore/secboot.
var WithSecbootSupport = false
type LoadChain struct {
*bootloader.BootFile
// Next is a list of alternative chains that can be loaded
// following the boot file.
Next []*LoadChain
}
// NewLoadChain returns a LoadChain corresponding to loading the given
// BootFile before any of the given next chains.
func NewLoadChain(bf bootloader.BootFile, next ...*LoadChain) *LoadChain {
return &LoadChain{
BootFile: &bf,
Next: next,
}
}
type SealKeyRequest struct {
// The key to seal
Key keys.EncryptionKey
// The key name; identical keys should have identical names
KeyName string
// The path to store the sealed key file. The same Key/KeyName
// can be stored under multiple KeyFile names for safety.
KeyFile string
}
// ModelForSealing provides information about the model for use in the context
// of (re)sealing the encryption keys.
type ModelForSealing interface {
Series() string
BrandID() string
Model() string
Grade() asserts.ModelGrade
SignKeyID() string
}
type SealKeyModelParams struct {
// The snap model
Model ModelForSealing
// The set of EFI binary load chains for the current device
// configuration
EFILoadChains []*LoadChain
// The kernel command line
KernelCmdlines []string
}
type TPMProvisionMode int
const (
TPMProvisionNone TPMProvisionMode = iota
// TPMProvisionFull indicates a full provisioning of the TPM
TPMProvisionFull
// TPMPartialReprovision indicates a partial reprovisioning of the TPM
// which was previously already provisioned by secboot. Existing lockout
// authorization data from TPMLockoutAuthFile will be used to authorize
// provisioning and will get overwritten in the process.
TPMPartialReprovision
)
type SealKeysParams struct {
// The parameters we're sealing the key to
ModelParams []*SealKeyModelParams
// The authorization policy update key file (only relevant for TPM)
TPMPolicyAuthKey *ecdsa.PrivateKey
// The path to the authorization policy update key file (only relevant for TPM,
// if empty the key will not be saved)
TPMPolicyAuthKeyFile string
// The handle at which to create a NV index for dynamic authorization policy revocation support
PCRPolicyCounterHandle uint32
}
type SealKeysWithFDESetupHookParams struct {
// Initial model to bind sealed keys to.
Model ModelForSealing
// AuxKey is the auxiliary key used to bind models.
AuxKey keys.AuxKey
// The path to the aux key file (if empty the key will not be
// saved)
AuxKeyFile string
}
type ResealKeysParams struct {
// The snap model parameters
ModelParams []*SealKeyModelParams
// The path to the sealed key files
KeyFiles []string
// The path to the authorization policy update key file (only relevant for TPM)
TPMPolicyAuthKeyFile string
}
// UnlockVolumeUsingSealedKeyOptions contains options for unlocking encrypted
// volumes using keys sealed to the TPM.
type UnlockVolumeUsingSealedKeyOptions struct {
// AllowRecoveryKey when true indicates activation with the recovery key
// will be attempted if activation with the sealed key failed.
AllowRecoveryKey bool
// WhichModel if invoked should return the device model
// assertion for which the disk is being unlocked.
WhichModel func() (*asserts.Model, error)
}
// UnlockMethod is the method that was used to unlock a volume.
type UnlockMethod int
const (
// NotUnlocked indicates that the device was either not unlocked or is not
// an encrypted device.
NotUnlocked UnlockMethod = iota
// UnlockedWithSealedKey indicates that the device was unlocked with the
// provided sealed key object.
UnlockedWithSealedKey
// UnlockedWithRecoveryKey indicates that the device was unlocked by the
// user providing the recovery key at the prompt.
UnlockedWithRecoveryKey
// UnlockedWithKey indicates that the device was unlocked with the provided
// key, which is not sealed.
UnlockedWithKey
// UnlockStatusUnknown indicates that the unlock status of the device is not clear.
UnlockStatusUnknown
)
// UnlockResult is the result of trying to unlock a volume.
type UnlockResult struct {
// FsDevice is the device with filesystem ready to mount.
// It is the activated device if encrypted or just
// the underlying device (same as PartDevice) if non-encrypted.
// FsDevice can be empty when none was found.
FsDevice string
// PartDevice is the underlying partition device.
// PartDevice can be empty when no device was found.
PartDevice string
// IsEncrypted indicates that PartDevice is encrypted.
IsEncrypted bool
// UnlockMethod is the method used to unlock the device. Valid values are
// - NotUnlocked
// - UnlockedWithRecoveryKey
// - UnlockedWithSealedKey
// - UnlockedWithKey
UnlockMethod UnlockMethod
}
// EncryptedPartitionName returns the name/label used by an encrypted partition
// corresponding to a given name.
func EncryptedPartitionName(name string) string {
return name + "-enc"
}
// MarkSuccessful marks the secure boot parts of the boot as
// successful.
//
//This means that the dictionary attack (DA) lockout counter is reset.
func MarkSuccessful() error {
sealingMethod, err := device.SealedKeysMethod(dirs.GlobalRootDir)
if err != nil && err != device.ErrNoSealedKeys {
return err
}
if sealingMethod == device.SealingMethodTPM {
lockoutAuthFile := device.TpmLockoutAuthUnder(dirs.SnapFDEDirUnderSave(dirs.SnapSaveDir))
// each unclean shtutdown will increase the DA lockout
// counter. So on a successful boot we need to clear
// this counter to avoid eventually hitting the
// snapcore/secboot:tpm2/provisioning.go limit of
// maxTries=32. Note that on a clean shtudown linux
// will call TPM2_Shutdown which ensure no DA lockout
// is increased.
if err := resetLockoutCounter(lockoutAuthFile); err != nil {
return err
}
}
return nil
}