Skip to content
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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copyright header missing.


import (
"errors"
"fmt"
"os"

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

const (
srkHandle tpmutil.Handle = 0x81000000
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be great to reference this to some documentation.


tpmPath string = "/dev/tpm0"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We don't need the "string" here (does not cause harm either though).

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Small wording tweak to the error message.

Suggested change
return fmt.Errorf("failed to open TPM device: %v", err)
return fmt.Errorf("cannot to open TPM device %q: %v", tpmPath, err)

}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to request permanent properties: %v", err)
return fmt.Errorf("cannot request permanent TPM 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to clear the TPM: %v", err)
return fmt.Errorf("cannot 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to create storage root key: %v", err)
return fmt.Errorf("cannot create storage for TPM 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to make storage root key persistent: %v", err)
return fmt.Errorf("cannot make storage for TPM 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to configure DA parameters: %v", err)
return fmt.Errorf("cannot configure DA parameters: %v", err)

I would also expand the DA acronym to whatever it stands for.

}

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to disable owner clear: %v", err)
return fmt.Errorf("cannot disable TPM 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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to set the lockout hierarchy authorization value: %v", err)
return fmt.Errorf("cannot set the TPM lockout hierarchy authorisation value: %v", err)

}

return nil
}

func RequestTPMClearUsingPPI() error {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is used only inside this package, it should probably be unexported - we can always export it if needed.

f, err := os.OpenFile(ppiPath, os.O_WRONLY, 0)
if err != nil {
return fmt.Errorf("failed to open request handle: %v", err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to open request handle: %v", err)
return fmt.Errorf("cannot open TPM request handle %q: %v", ppiPath, err)

}
defer f.Close()

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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return fmt.Errorf("failed to submit request: %v", err)
return fmt.Errorf("cannot submit TPM 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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
logger.Noticef("Creating new ubuntu-data")
logger.Noticef("Creating new ubuntu-data partition")

It would be good to always refer to partitions as "foo" partition, because the bare name like ubuntu-data is not associated with partitions in any way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please just use syscall.Sync instead. No need to fork for this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, excellent, that's much better, thank you.

}

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
7 changes: 7 additions & 0 deletions vendor/vendor.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@
"revision": "97646858c46433e4afb3432ad28c12e968efa298",
"revisionTime": "2017-08-22T15:24:03Z"
},
{
"checksumSHA1": "M1jObMAw/YV+Rc5PccfmJuuxLa0=",
"origin": "github.com/chrisccoulson/go-tpm",
"path": "github.com/google/go-tpm",
"revision": "c6c7cb7465ae50e13263f2f8ff33f57d1f9859bc",
"revisionTime": "2019-07-08T13:36:22Z"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This will need the following extra line:

"tree": true

},
{
"checksumSHA1": "iIUYZyoanCQQTUaWsu8b+iOSPt4=",
"path": "github.com/gorilla/context",
Expand Down