Skip to content

Commit

Permalink
recovery: integrate TPM provisioning
Browse files Browse the repository at this point in the history
Execute TPM provisioning on install using Chris Coulson's FDE utils code
from https://github.com/chrisccoulson/ubuntu-core-fde-utils. This also
requires google/go-tpm#109.

Try to provision the TPM as late as possible to prevent a situation
where the installation fails after the TPM is provisioned. Provisioning
happens just before sealing the LUKS device keyfile and the lockout
authorization value is stored inside the encrypted partition.

Signed-off-by: Claudio Matsuoka <[email protected]>
  • Loading branch information
cmatsuoka committed Jul 9, 2019
1 parent 827e847 commit e2aa31f
Show file tree
Hide file tree
Showing 3 changed files with 173 additions and 19 deletions.
26 changes: 14 additions & 12 deletions recovery/device.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ import (
"bufio"
"fmt"
"io/ioutil"
"math/rand"
"os"
"os/exec"
"path"
Expand Down Expand Up @@ -85,7 +84,7 @@ func (d *DiskDevice) CreatePartition(size uint64, label string) error {
return nil
}

func (d *DiskDevice) CreateLUKSPartition(size uint64, label string, keyfile string, keyfileSize int, cryptdev string) error {
func (d *DiskDevice) CreateLUKSPartition(size uint64, label string, keyBuffer []byte, cryptdev string) error {
logger.Noticef("Create partition %q", label)
cmd := exec.Command("sfdisk", "--no-reread", "-a", d.node)
stdin, err := cmd.StdinPipe()
Expand Down Expand Up @@ -115,18 +114,16 @@ func (d *DiskDevice) CreateLUKSPartition(size uint64, label string, keyfile stri
// FIXME: determine partition name in a civilized way
partdev := d.partDev(4)

// Set up LUKS device
logger.Noticef("Create LUKS keyfile")
buffer := make([]byte, keyfileSize)
rand.Read(buffer)
if err := ioutil.WriteFile(keyfile, buffer, 0400); err != nil {
return fmt.Errorf("cannot create keyfile %s: %s", keyfile, err)
}

// Don't remove this delay, prevents kernel crash
// Don't remove this delay, it prevents a kernel crash
// see https://bugs.launchpad.net/ubuntu/+source/linux/+bug/1835279
time.Sleep(1 * time.Second)

// Create a temporary unsealed keyfile on a RAM-backed filesystem
keyfile := "/run/tmpkeyfile"
if err := ioutil.WriteFile(keyfile, keyBuffer, 0400); err != nil {
return fmt.Errorf("cannot create keyfile: %s", err)
}

logger.Noticef("Create LUKS device on %s", partdev)
if output, err := exec.Command("cryptsetup", "-q", "--type", "luks2", "--key-file", keyfile,
"--pbkdf-memory", "100000", "luksFormat", partdev).CombinedOutput(); err != nil {
Expand All @@ -142,7 +139,12 @@ func (d *DiskDevice) CreateLUKSPartition(size uint64, label string, keyfile stri
return osutil.OutputErr(output, fmt.Errorf("cannot open LUKS device on %s: %s", partdev, err))
}

// Ok, now this is ugly. We'll have to see how to handle this properly without udev.
// FIXME: use secure delete
if err := os.Remove(keyfile); err != nil {
logger.Noticef("can't remove keyfile: %s", err)
}

// FIXME: Ok, now this is ugly. We'll have to see how to handle this properly.
logger.Noticef("Hack: create LUKS device symlink")
if err := os.Symlink("../dm-0", cryptdev); err != nil {
return fmt.Errorf("cannot create LUKS device symlink: %s", err)
Expand Down
100 changes: 100 additions & 0 deletions recovery/fdeutils/provisioning.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package fdeutils

import (
"errors"
"fmt"
"os"

"github.com/google/go-tpm/tpm2"
"github.com/google/go-tpm/tpmutil"
)

const (
srkHandle tpmutil.Handle = 0x81000000

tpmPath string = "/dev/tpm0"
ppiPath string = "/sys/class/tpm/tpm0/ppi/request"

permanentProps uint32 = 0x00000200
lockoutAuthSet uint32 = 1 << 2
disableClear uint32 = 1 << 8

clearPPIRequest string = "5"
)

var (
ErrClearRequiresPPI = errors.New("clearing requires the use of the Physical Presence Interface")

srkTemplate = tpm2.Public{
Type: tpm2.AlgRSA,
NameAlg: tpm2.AlgSHA256,
Attributes: tpm2.FlagFixedTPM | tpm2.FlagFixedParent | tpm2.FlagSensitiveDataOrigin |
tpm2.FlagUserWithAuth | tpm2.FlagRestricted | tpm2.FlagDecrypt,
AuthPolicy: nil,
RSAParameters: &tpm2.RSAParams{
Symmetric: &tpm2.SymScheme{
Alg: tpm2.AlgAES,
KeyBits: 128,
Mode: tpm2.AlgCFB},
KeyBits: 2048,
Exponent: 0,
ModulusRaw: make([]byte, 256)}}
)

func ProvisionTPM(lockoutAuth []byte) error {
rw, err := tpm2.OpenTPM(tpmPath)
if err != nil {
return fmt.Errorf("failed to open TPM device: %v", err)
}

c, _, err := tpm2.GetCapability(rw, tpm2.CapabilityTPMProperties, 1, permanentProps)
if err != nil {
return fmt.Errorf("failed to request permanent properties: %v", err)
}

p := c[0].(tpm2.TaggedProperty).Value
if p&lockoutAuthSet > 0 || p&disableClear > 0 {
return ErrClearRequiresPPI
}

if err := tpm2.Clear(rw, tpm2.HandleLockout, ""); err != nil {
return fmt.Errorf("failed to clear the TPM: %v", err)
}

h, _, err := tpm2.CreatePrimary(rw, tpm2.HandleOwner, tpm2.PCRSelection{}, "", "", srkTemplate)
if err != nil {
return fmt.Errorf("failed to create storage root key: %v", err)
}

if err := tpm2.EvictControl(rw, "", tpm2.HandleOwner, h, srkHandle); err != nil {
return fmt.Errorf("failed to make storage root key persistent: %v", err)
}

if err := tpm2.SetDictionaryAttackParameters(rw, 32, 7200, 86400, ""); err != nil {
return fmt.Errorf("failed to configure DA parameters: %v", err)
}

if err := tpm2.DisableOwnerClear(rw, ""); err != nil {
return fmt.Errorf("failed to disable owner clear: %v", err)
}

if err := tpm2.HierarchyChangeAuth(rw, tpm2.HandleLockout, "", string(lockoutAuth)); err != nil {
return fmt.Errorf("failed to set the lockout hierarchy authorization value: %v", err)
}

return nil
}

func RequestTPMClearUsingPPI() error {
f, err := os.OpenFile(ppiPath, os.O_WRONLY, 0)
if err != nil {
return fmt.Errorf("failed to open request handle: %v", err)
}
defer f.Close()

if _, err := f.WriteString(clearPPIRequest); err != nil {
return fmt.Errorf("failed to submit request: %v", err)
}

return nil
}
66 changes: 59 additions & 7 deletions recovery/recovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
package recovery

import (
"crypto/rand"
"fmt"
"io/ioutil"
"os"
Expand All @@ -32,6 +33,7 @@ import (
"github.com/snapcore/snapd/bootloader/grubenv"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/recovery/fdeutils"
)

const (
Expand Down Expand Up @@ -142,8 +144,6 @@ func Install(version string) error {
mntSysRecover := "/mnt/sys-recover"
mntSystemBoot := "/mnt/system-boot"

keyfile := path.Join(mntSystemBoot, "keyfile")

if err := mountFilesystem("ubuntu-boot", mntSystemBoot); err != nil {
return err
}
Expand All @@ -152,8 +152,16 @@ func Install(version string) error {

cryptdev := "ubuntu-data"

logger.Noticef("Create LUKS key")
keySize := 4 * sizeKB
keyBuffer := make([]byte, keySize)
n, err := rand.Read(keyBuffer)
if n != keySize || err != nil {
return fmt.Errorf("cannot create LUKS key: %s", err)
}

logger.Noticef("Install recovery %s", version)
if err := createWritable(keyfile, 4*sizeKB, cryptdev); err != nil {
if err := createWritable(keyBuffer, cryptdev); err != nil {
logger.Noticef("cannot create writable: %s", err)
return err
}
Expand All @@ -175,6 +183,31 @@ func Install(version string) error {
return err
}

logger.Noticef("Create lockout authorization")
lockoutAuth := make([]byte, 16)
n, err = rand.Read(lockoutAuth)
if n != 16 || err != nil {
return fmt.Errorf("cannot create lockout authorization: %s", err)
}

logger.Noticef("Provisioning the TPM")
if err := fdeutils.ProvisionTPM(lockoutAuth); err != nil {
logger.Noticef("error provisioning the TPM: %s", err)
return fmt.Errorf("cannot provision TPM: %s", err)
}

if err := storeKeyfile(mntSystemBoot, keyBuffer); err != nil {
return fmt.Errorf("cannot store keyfile: %s", err)
}

if err := storeLockoutAuth(mntWritable, lockoutAuth); err != nil {
return fmt.Errorf("cannot store lockout authorization: %s", err)
}

if err := exec.Command("sync").Run(); err != nil {
return fmt.Errorf("cannot sync: %s", err)
}

if err := umount(mntWritable); err != nil {
return err
}
Expand All @@ -196,15 +229,15 @@ func Install(version string) error {
return nil
}

func createWritable(keyfile string, keyfileSize int, cryptdev string) error {
logger.Noticef("Creating new writable")
func createWritable(keyBuffer []byte, cryptdev string) error {
logger.Noticef("Creating new ubuntu-data")
disk := &DiskDevice{}
if err := disk.FindFromPartLabel("ubuntu-boot"); err != nil {
return fmt.Errorf("cannot determine boot device: %s", err)
}

// FIXME: get values from gadget, system
err := disk.CreateLUKSPartition(1000*sizeMB, "ubuntu-data", keyfile, keyfileSize, cryptdev)
err := disk.CreateLUKSPartition(1000*sizeMB, "ubuntu-data", keyBuffer, cryptdev)
if err != nil {
return fmt.Errorf("cannot create new ubuntu-data: %s", err)
}
Expand All @@ -226,6 +259,25 @@ func mountFilesystem(label string, mountpoint string) error {
return nil
}

func storeKeyfile(dir string, buffer []byte) error {
// TODO: seal keyfile
if err := ioutil.WriteFile(path.Join(dir, "keyfile"), buffer, 0400); err != nil {
return err
}

// Don't remove this sync, it prevents file corruption on vfat
if err := exec.Command("sync").Run(); err != nil {
return fmt.Errorf("cannot sync: %s", err)
}

return nil
}

// The lockout authorization file is stored in the encrypted partition
func storeLockoutAuth(dir string, lockoutAuth []byte) error {
return ioutil.WriteFile(path.Join(dir, "system-data/lockoutAuth"), lockoutAuth, 0400)
}

func updateRecovery(mntWritable, mntSysRecover, mntSystemBoot, version string) (core string, kernel string, err error) {
logger.Noticef("Populating new writable")

Expand Down Expand Up @@ -326,7 +378,7 @@ func extractKernel(kernelPath, mntSystemBoot string) error {
}
}

// Don't remove this sync, prevents corrupted files on vfat
// Don't remove this sync, it prevents file corruption on vfat
if err := exec.Command("sync").Run(); err != nil {
return fmt.Errorf("cannot sync filesystems: %s", err)
}
Expand Down

0 comments on commit e2aa31f

Please sign in to comment.