diff --git a/recovery/device.go b/recovery/device.go index a43a69125ec..e105c391e56 100644 --- a/recovery/device.go +++ b/recovery/device.go @@ -20,9 +20,9 @@ package recovery import ( "bufio" + "bytes" "fmt" "io/ioutil" - "math/rand" "os" "os/exec" "path" @@ -52,22 +52,9 @@ func (d *DiskDevice) FindFromPartLabel(label string) error { func (d *DiskDevice) CreatePartition(size uint64, label string) error { logger.Noticef("Create partition %q", label) - cmd := exec.Command("sfdisk", "--no-reread", "-a", d.node) - stdin, err := cmd.StdinPipe() - if err != nil { - return err - } - if err := cmd.Start(); err != nil { - return fmt.Errorf("cannot create partition: %s", err) - } - if _, err := stdin.Write([]byte(fmt.Sprintf(",%d,,,", size/sizeSector))); err != nil { - return fmt.Errorf("cannot write to sfdisk pipe: %s", err) - } - if err := stdin.Close(); err != nil { - return fmt.Errorf("cannot close fdisk pipe: %s", err) - } - if err := cmd.Wait(); err != nil { - return fmt.Errorf("cannot run sfdisk: %s", err) + if output, err := pipeRun([]byte(fmt.Sprintf(",%d,,,", size/sizeSector)), + "sfdisk", "--no-reread", "-a", d.node); err != nil { + return osutil.OutputErr(output, fmt.Errorf("cannot create partition: %s", err)) } // Re-read partition table @@ -85,12 +72,12 @@ 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() if err != nil { - return err + return fmt.Errorf("cannot get sfdisk stdin: %s", err) } if err := cmd.Start(); err != nil { return fmt.Errorf("cannot create partition: %s", err) @@ -115,34 +102,27 @@ 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) + // Write key to stdin to avoid creating a temporary file 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 { - return osutil.OutputErr(output, fmt.Errorf("cannot format LUKS device on %s: %s", partdev, err)) + if output, err := pipeRun(keyBuffer, "cryptsetup", "-q", "--type", "luks2", "--key-file", "-", + "--pbkdf-memory", "100000", "luksFormat", partdev); err != nil { + return osutil.OutputErr(output, fmt.Errorf("cannot format LUKS device: %s", err)) } time.Sleep(1 * time.Second) logger.Noticef("Open LUKS device") - if output, err := exec.Command("sh", "-c", fmt.Sprintf("LD_PRELOAD=/lib/no-udev.so cryptsetup "+ - "--type luks2 --key-file %s --pbkdf-memory 100000 open %s %s", keyfile, partdev, - path.Base(cryptdev))).CombinedOutput(); err != nil { + if output, err := pipeRun(keyBuffer, "sh", "-c", fmt.Sprintf("LD_PRELOAD=/lib/no-udev.so cryptsetup "+ + "--type luks2 --key-file - --pbkdf-memory 100000 open %s %s", partdev, + path.Base(cryptdev))); err != nil { 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: 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) @@ -159,6 +139,35 @@ func (d *DiskDevice) CreateLUKSPartition(size uint64, label string, keyfile stri return nil } +func pipeRun(input []byte, name string, args ...string) ([]byte, error) { + cmd := exec.Command(name, args...) + stdin, err := cmd.StdinPipe() + var out bytes.Buffer + // FIXME: use combined output + cmd.Stderr = &out + if err != nil { + return nil, err + } + if err = cmd.Start(); err != nil { + return nil, err + } + n, err := stdin.Write(input) + if err != nil { + return nil, err + } + if n != len(input) { + err = fmt.Errorf("short write (%d)", n) + return nil, err + } + if err = stdin.Close(); err != nil { + return nil, err + } + if err = cmd.Wait(); err != nil { + return nil, err + } + return out.Bytes(), nil +} + func createCrypttab(partdev, keyfile, cryptdev string) error { buffer := fmt.Sprintf("%s %s %s luks\n", cryptdev, partdev, keyfile) if err := ioutil.WriteFile(keyfile, []byte(buffer), 0644); err != nil { diff --git a/recovery/recovery.go b/recovery/recovery.go index 739f2d0ae1f..42dc65f4c58 100644 --- a/recovery/recovery.go +++ b/recovery/recovery.go @@ -21,13 +21,17 @@ package recovery import ( + "crypto/rand" "fmt" "io/ioutil" "os" "os/exec" "path" + "syscall" "time" + "github.com/chrisccoulson/ubuntu-core-fde-utils" + "github.com/snapcore/snapd/bootloader" "github.com/snapcore/snapd/bootloader/grubenv" "github.com/snapcore/snapd/logger" @@ -142,8 +146,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 } @@ -152,8 +154,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 } @@ -175,6 +185,29 @@ 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 := fdeutil.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) + } + + syscall.Sync() + if err := umount(mntWritable); err != nil { return err } @@ -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 partition") 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) } @@ -226,6 +259,23 @@ 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 + syscall.Sync() + + 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") @@ -326,10 +376,8 @@ func extractKernel(kernelPath, mntSystemBoot string) error { } } - // Don't remove this sync, prevents corrupted files on vfat - if err := exec.Command("sync").Run(); err != nil { - return fmt.Errorf("cannot sync filesystems: %s", err) - } + // Don't remove this sync, it prevents file corruption on vfat + syscall.Sync() if err := umount(mntKernelSnap); err != nil { return err diff --git a/vendor/vendor.json b/vendor/vendor.json index c383e5dd57f..f12ebc82afb 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -6,6 +6,12 @@ "path": "context", "revision": "" }, + { + "checksumSHA1": "X9uJjtPte7hdg0/LSEtnDD4v6Iw=", + "path": "github.com/chrisccoulson/ubuntu-core-fde-utils", + "revision": "49b2130cfa1a95f940eaf348b3a71b3f7af344ba", + "revisionTime": "2019-07-11T08:52:23Z" + }, { "checksumSHA1": "zg16zjZTQ9R89+UOLmEZxHgxDtM=", "path": "github.com/coreos/go-systemd/activation", @@ -24,6 +30,14 @@ "revision": "97646858c46433e4afb3432ad28c12e968efa298", "revisionTime": "2017-08-22T15:24:03Z" }, + { + "checksumSHA1": "mkW1FNv2atdJblBoNsGkAfvec5w=", + "origin": "github.com/chrisccoulson/go-tpm", + "path": "github.com/google/go-tpm", + "revision": "c6c7cb7465ae50e13263f2f8ff33f57d1f9859bc", + "revisionTime": "2019-07-08T13:36:22Z", + "tree": true + }, { "checksumSHA1": "iIUYZyoanCQQTUaWsu8b+iOSPt4=", "path": "github.com/gorilla/context",