github.com/stolowski/snapd@v0.0.0-20210407085831-115137ce5a22/secboot/secboot_tpm_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  // +build !nosecboot
     3  
     4  /*
     5   * Copyright (C) 2020 Canonical Ltd
     6   *
     7   * This program is free software: you can redistribute it and/or modify
     8   * it under the terms of the GNU General Public License version 3 as
     9   * published by the Free Software Foundation.
    10   *
    11   * This program is distributed in the hope that it will be useful,
    12   * but WITHOUT ANY WARRANTY; without even the implied warranty of
    13   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14   * GNU General Public License for more details.
    15   *
    16   * You should have received a copy of the GNU General Public License
    17   * along with this program.  If not, see <http://www.gnu.org/licenses/>.
    18   *
    19   */
    20  
    21  package secboot_test
    22  
    23  import (
    24  	"crypto/ecdsa"
    25  	"encoding/base64"
    26  	"errors"
    27  	"fmt"
    28  	"io"
    29  	"io/ioutil"
    30  	"os"
    31  	"os/exec"
    32  	"path/filepath"
    33  	"testing"
    34  	"time"
    35  
    36  	"github.com/canonical/go-tpm2"
    37  	sb "github.com/snapcore/secboot"
    38  	. "gopkg.in/check.v1"
    39  
    40  	"github.com/snapcore/snapd/asserts"
    41  	"github.com/snapcore/snapd/bootloader"
    42  	"github.com/snapcore/snapd/bootloader/efi"
    43  	"github.com/snapcore/snapd/dirs"
    44  	"github.com/snapcore/snapd/osutil"
    45  	"github.com/snapcore/snapd/osutil/disks"
    46  	"github.com/snapcore/snapd/secboot"
    47  	"github.com/snapcore/snapd/snap"
    48  	"github.com/snapcore/snapd/snap/snapfile"
    49  	"github.com/snapcore/snapd/snap/squashfs"
    50  	"github.com/snapcore/snapd/testutil"
    51  )
    52  
    53  func TestSecboot(t *testing.T) { TestingT(t) }
    54  
    55  type secbootSuite struct {
    56  	testutil.BaseTest
    57  }
    58  
    59  var _ = Suite(&secbootSuite{})
    60  
    61  func (s *secbootSuite) SetUpTest(c *C) {
    62  	rootDir := c.MkDir()
    63  	err := os.MkdirAll(filepath.Join(rootDir, "/run"), 0755)
    64  	c.Assert(err, IsNil)
    65  	dirs.SetRootDir(rootDir)
    66  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    67  }
    68  
    69  func (s *secbootSuite) TestCheckKeySealingSupported(c *C) {
    70  	sbEmpty := []uint8{}
    71  	sbEnabled := []uint8{1}
    72  	sbDisabled := []uint8{0}
    73  	efiNotSupported := []uint8(nil)
    74  	tpmErr := errors.New("TPM error")
    75  
    76  	type testCase struct {
    77  		tpmErr     error
    78  		tpmEnabled bool
    79  		sbData     []uint8
    80  		err        string
    81  	}
    82  	for i, tc := range []testCase{
    83  		// happy case
    84  		{tpmErr: nil, tpmEnabled: true, sbData: sbEnabled, err: ""},
    85  		// secure boot EFI var is empty
    86  		{tpmErr: nil, tpmEnabled: true, sbData: sbEmpty, err: "secure boot variable does not exist"},
    87  		// secure boot is disabled
    88  		{tpmErr: nil, tpmEnabled: true, sbData: sbDisabled, err: "secure boot is disabled"},
    89  		// EFI not supported
    90  		{tpmErr: nil, tpmEnabled: true, sbData: efiNotSupported, err: "not a supported EFI system"},
    91  		// TPM connection error
    92  		{tpmErr: tpmErr, sbData: sbEnabled, err: "cannot connect to TPM device: TPM error"},
    93  		// TPM was detected but it's not enabled
    94  		{tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"},
    95  		// No TPM device
    96  		{tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"},
    97  	} {
    98  		c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err)
    99  
   100  		_, restore := mockSbTPMConnection(c, tc.tpmErr)
   101  		defer restore()
   102  
   103  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   104  			return tc.tpmEnabled
   105  		})
   106  		defer restore()
   107  
   108  		var vars map[string][]byte
   109  		if tc.sbData != nil {
   110  			vars = map[string][]byte{"SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c": tc.sbData}
   111  		}
   112  		restoreEfiVars := efi.MockVars(vars, nil)
   113  		defer restoreEfiVars()
   114  
   115  		err := secboot.CheckKeySealingSupported()
   116  		if tc.err == "" {
   117  			c.Assert(err, IsNil)
   118  		} else {
   119  			c.Assert(err, ErrorMatches, tc.err)
   120  		}
   121  	}
   122  }
   123  
   124  func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) {
   125  	for _, tc := range []struct {
   126  		tpmErr     error
   127  		tpmEnabled bool
   128  		callNum    int
   129  		err        string
   130  	}{
   131  		{
   132  			// normal connection to the TPM device
   133  			tpmErr: nil, tpmEnabled: true, callNum: 1, err: "",
   134  		},
   135  		{
   136  			// TPM device exists but returns error
   137  			tpmErr: errors.New("tpm error"), callNum: 0,
   138  			err: "cannot measure snap system epoch: cannot open TPM connection: tpm error",
   139  		},
   140  		{
   141  			// TPM device exists but is disabled
   142  			tpmErr: nil, tpmEnabled: false,
   143  		},
   144  		{
   145  			// TPM device does not exist
   146  			tpmErr: sb.ErrNoTPM2Device,
   147  		},
   148  	} {
   149  		mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   150  		defer restore()
   151  
   152  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   153  			return tc.tpmEnabled
   154  		})
   155  		defer restore()
   156  
   157  		calls := 0
   158  		restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error {
   159  			calls++
   160  			c.Assert(tpm, Equals, mockTpm)
   161  			c.Assert(pcrIndex, Equals, 12)
   162  			return nil
   163  		})
   164  		defer restore()
   165  
   166  		err := secboot.MeasureSnapSystemEpochWhenPossible()
   167  		if tc.err == "" {
   168  			c.Assert(err, IsNil)
   169  		} else {
   170  			c.Assert(err, ErrorMatches, tc.err)
   171  		}
   172  		c.Assert(calls, Equals, tc.callNum)
   173  	}
   174  }
   175  
   176  func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) {
   177  	for i, tc := range []struct {
   178  		tpmErr     error
   179  		tpmEnabled bool
   180  		modelErr   error
   181  		callNum    int
   182  		err        string
   183  	}{
   184  		{
   185  			// normal connection to the TPM device
   186  			tpmErr: nil, tpmEnabled: true, modelErr: nil, callNum: 1, err: "",
   187  		},
   188  		{
   189  			// normal connection to the TPM device with model error
   190  			tpmErr: nil, tpmEnabled: true, modelErr: errors.New("model error"), callNum: 0,
   191  			err: "cannot measure snap model: model error",
   192  		},
   193  		{
   194  			// TPM device exists but returns error
   195  			tpmErr: errors.New("tpm error"), callNum: 0,
   196  			err: "cannot measure snap model: cannot open TPM connection: tpm error",
   197  		},
   198  		{
   199  			// TPM device exists but is disabled
   200  			tpmErr: nil, tpmEnabled: false,
   201  		},
   202  		{
   203  			// TPM device does not exist
   204  			tpmErr: sb.ErrNoTPM2Device,
   205  		},
   206  	} {
   207  		c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled)
   208  		mockModel := &asserts.Model{}
   209  
   210  		mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   211  		defer restore()
   212  
   213  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   214  			return tc.tpmEnabled
   215  		})
   216  		defer restore()
   217  
   218  		calls := 0
   219  		restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error {
   220  			calls++
   221  			c.Assert(tpm, Equals, mockTpm)
   222  			c.Assert(model, Equals, mockModel)
   223  			c.Assert(pcrIndex, Equals, 12)
   224  			return nil
   225  		})
   226  		defer restore()
   227  
   228  		findModel := func() (*asserts.Model, error) {
   229  			if tc.modelErr != nil {
   230  				return nil, tc.modelErr
   231  			}
   232  			return mockModel, nil
   233  		}
   234  
   235  		err := secboot.MeasureSnapModelWhenPossible(findModel)
   236  		if tc.err == "" {
   237  			c.Assert(err, IsNil)
   238  		} else {
   239  			c.Assert(err, ErrorMatches, tc.err)
   240  		}
   241  		c.Assert(calls, Equals, tc.callNum)
   242  	}
   243  }
   244  
   245  func (s *secbootSuite) TestLockTPMSealedKeys(c *C) {
   246  	tt := []struct {
   247  		tpmErr     error
   248  		tpmEnabled bool
   249  		lockOk     bool
   250  		expError   string
   251  	}{
   252  		// can't connect to tpm
   253  		{
   254  			tpmErr:   fmt.Errorf("failed to connect to tpm"),
   255  			expError: "cannot lock TPM: failed to connect to tpm",
   256  		},
   257  		// no TPM2 device, shouldn't return an error
   258  		{
   259  			tpmErr: sb.ErrNoTPM2Device,
   260  		},
   261  		// tpm is not enabled but we can lock it
   262  		{
   263  			tpmEnabled: false,
   264  			lockOk:     true,
   265  		},
   266  		// can't lock pcr protection profile
   267  		{
   268  			tpmEnabled: true,
   269  			lockOk:     false,
   270  			expError:   "block failed",
   271  		},
   272  		// tpm enabled, we can lock it
   273  		{
   274  			tpmEnabled: true,
   275  			lockOk:     true,
   276  		},
   277  	}
   278  
   279  	for _, tc := range tt {
   280  		mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr)
   281  		defer restoreConnect()
   282  
   283  		restore := secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   284  			return tc.tpmEnabled
   285  		})
   286  		defer restore()
   287  
   288  		sbBlockPCRProtectionPolicesCalls := 0
   289  		restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb.TPMConnection, pcrs []int) error {
   290  			sbBlockPCRProtectionPolicesCalls++
   291  			c.Assert(tpm, Equals, mockSbTPM)
   292  			c.Assert(pcrs, DeepEquals, []int{12})
   293  			if tc.lockOk {
   294  				return nil
   295  			}
   296  			return errors.New("block failed")
   297  		})
   298  		defer restore()
   299  
   300  		err := secboot.LockTPMSealedKeys()
   301  		if tc.expError == "" {
   302  			c.Assert(err, IsNil)
   303  		} else {
   304  			c.Assert(err, ErrorMatches, tc.expError)
   305  		}
   306  		// if there was no TPM connection error, we should have tried to lock it
   307  		if tc.tpmErr == nil {
   308  			c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 1)
   309  		} else {
   310  			c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 0)
   311  		}
   312  	}
   313  }
   314  
   315  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
   316  
   317  	// setup mock disks to use for locating the partition
   318  	// restore := disks.MockMountPointDisksToPartitionMapping()
   319  	// defer restore()
   320  
   321  	mockDiskWithEncDev := &disks.MockDiskMapping{
   322  		FilesystemLabelToPartUUID: map[string]string{
   323  			"name-enc": "enc-dev-partuuid",
   324  		},
   325  	}
   326  
   327  	mockDiskWithoutAnyDev := &disks.MockDiskMapping{
   328  		FilesystemLabelToPartUUID: map[string]string{},
   329  	}
   330  
   331  	mockDiskWithUnencDev := &disks.MockDiskMapping{
   332  		FilesystemLabelToPartUUID: map[string]string{
   333  			"name": "unenc-dev-partuuid",
   334  		},
   335  	}
   336  
   337  	for idx, tc := range []struct {
   338  		tpmErr              error
   339  		keyfile             string // the keyfile to be used to unseal
   340  		tpmEnabled          bool   // TPM storage and endorsement hierarchies disabled, only relevant if TPM available
   341  		hasEncdev           bool   // an encrypted device exists
   342  		rkAllow             bool   // allow recovery key activation
   343  		rkErr               error  // recovery key unlock error, only relevant if TPM not available
   344  		activated           bool   // the activation operation succeeded
   345  		activateErr         error  // the activation error
   346  		err                 string
   347  		skipDiskEnsureCheck bool // whether to check to ensure the mock disk contains the device label
   348  		expUnlockMethod     secboot.UnlockMethod
   349  		disk                *disks.MockDiskMapping
   350  	}{
   351  		{
   352  			// happy case with tpm and encrypted device
   353  			tpmEnabled: true, hasEncdev: true,
   354  			activated:       true,
   355  			disk:            mockDiskWithEncDev,
   356  			expUnlockMethod: secboot.UnlockedWithSealedKey,
   357  		}, {
   358  			// happy case with tpm and encrypted device
   359  			// with an alternative keyfile
   360  			tpmEnabled: true, hasEncdev: true,
   361  			activated:       true,
   362  			disk:            mockDiskWithEncDev,
   363  			keyfile:         "some-other-keyfile",
   364  			expUnlockMethod: secboot.UnlockedWithSealedKey,
   365  		}, {
   366  			// device activation fails
   367  			tpmEnabled: true, hasEncdev: true,
   368  			err:  "cannot activate encrypted device .*: activation error",
   369  			disk: mockDiskWithEncDev,
   370  		}, {
   371  			// device activation fails
   372  			tpmEnabled: true, hasEncdev: true,
   373  			err:  "cannot activate encrypted device .*: activation error",
   374  			disk: mockDiskWithEncDev,
   375  		}, {
   376  			// happy case without encrypted device
   377  			tpmEnabled: true,
   378  			disk:       mockDiskWithUnencDev,
   379  		}, {
   380  			// happy case with tpm and encrypted device, activation
   381  			// with recovery key
   382  			tpmEnabled: true, hasEncdev: true, activated: true,
   383  			activateErr: &sb.ActivateWithTPMSealedKeyError{
   384  				// activation error with nil recovery key error
   385  				// implies volume activated successfully using
   386  				// the recovery key,
   387  				RecoveryKeyUsageErr: nil,
   388  			},
   389  			disk:            mockDiskWithEncDev,
   390  			expUnlockMethod: secboot.UnlockedWithRecoveryKey,
   391  		}, {
   392  			// tpm and encrypted device, successful activation, but
   393  			// recovery key non-nil is an unexpected state
   394  			tpmEnabled: true, hasEncdev: true, activated: true,
   395  			activateErr: &sb.ActivateWithTPMSealedKeyError{
   396  				RecoveryKeyUsageErr: fmt.Errorf("unexpected"),
   397  			},
   398  			expUnlockMethod: secboot.UnlockStatusUnknown,
   399  			err:             `internal error: volume activated with unexpected error: .* \(unexpected\)`,
   400  			disk:            mockDiskWithEncDev,
   401  		}, {
   402  			// tpm error, no encrypted device
   403  			tpmErr: errors.New("tpm error"),
   404  			disk:   mockDiskWithUnencDev,
   405  		}, {
   406  			// tpm error, has encrypted device
   407  			tpmErr: errors.New("tpm error"), hasEncdev: true,
   408  			err:  `cannot unlock encrypted device "name": tpm error`,
   409  			disk: mockDiskWithEncDev,
   410  		}, {
   411  			// tpm disabled, no encrypted device
   412  			disk: mockDiskWithUnencDev,
   413  		}, {
   414  			// tpm disabled, has encrypted device, unlocked using the recovery key
   415  			hasEncdev:       true,
   416  			rkAllow:         true,
   417  			disk:            mockDiskWithEncDev,
   418  			expUnlockMethod: secboot.UnlockedWithRecoveryKey,
   419  		}, {
   420  			// tpm disabled, has encrypted device, recovery key unlocking fails
   421  			hasEncdev: true, rkErr: errors.New("cannot unlock with recovery key"),
   422  			rkAllow: true,
   423  			disk:    mockDiskWithEncDev,
   424  			err:     `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
   425  		}, {
   426  			// no tpm, has encrypted device, unlocked using the recovery key
   427  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
   428  			rkAllow:         true,
   429  			disk:            mockDiskWithEncDev,
   430  			expUnlockMethod: secboot.UnlockedWithRecoveryKey,
   431  		}, {
   432  			// no tpm, has encrypted device, unlocking with recovery key not allowed
   433  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
   434  			disk: mockDiskWithEncDev,
   435  			err:  `cannot activate encrypted device ".*/enc-dev-partuuid": activation error`,
   436  		}, {
   437  			// no tpm, has encrypted device, recovery key unlocking fails
   438  			rkErr:  errors.New("cannot unlock with recovery key"),
   439  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
   440  			rkAllow: true,
   441  			disk:    mockDiskWithEncDev,
   442  			err:     `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
   443  		}, {
   444  			// no tpm, no encrypted device
   445  			tpmErr: sb.ErrNoTPM2Device,
   446  			disk:   mockDiskWithUnencDev,
   447  		}, {
   448  			// no disks at all
   449  			disk:                mockDiskWithoutAnyDev,
   450  			skipDiskEnsureCheck: true,
   451  			// error is specifically for failing to find name, NOT name-enc, we
   452  			// will properly fall back to looking for name if we didn't find
   453  			// name-enc
   454  			err: "error enumerating partitions for disk to find unencrypted device \"name\": filesystem label \"name\" not found",
   455  		},
   456  	} {
   457  		randomUUID := fmt.Sprintf("random-uuid-for-test-%d", idx)
   458  		restore := secboot.MockRandomKernelUUID(func() string {
   459  			return randomUUID
   460  		})
   461  		defer restore()
   462  
   463  		c.Logf("tc %v: %+v", idx, tc)
   464  		_, restoreConnect := mockSbTPMConnection(c, tc.tpmErr)
   465  		defer restoreConnect()
   466  
   467  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   468  			return tc.tpmEnabled
   469  		})
   470  		defer restore()
   471  
   472  		defaultDevice := "name"
   473  
   474  		fsLabel := defaultDevice
   475  		if tc.hasEncdev {
   476  			fsLabel += "-enc"
   477  		}
   478  		partuuid, ok := tc.disk.FilesystemLabelToPartUUID[fsLabel]
   479  		if !tc.skipDiskEnsureCheck {
   480  			c.Assert(ok, Equals, true)
   481  		}
   482  		devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid)
   483  
   484  		expKeyPath := tc.keyfile
   485  		if expKeyPath == "" {
   486  			expKeyPath = "vanilla-keyfile"
   487  		}
   488  
   489  		restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath,
   490  			keyPath string, pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error) {
   491  			c.Assert(volumeName, Equals, "name-"+randomUUID)
   492  			c.Assert(sourceDevicePath, Equals, devicePath)
   493  			c.Assert(keyPath, Equals, expKeyPath)
   494  			if tc.rkAllow {
   495  				c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{
   496  					PassphraseTries:  1,
   497  					RecoveryKeyTries: 3,
   498  					KeyringPrefix:    "ubuntu-fde",
   499  				})
   500  			} else {
   501  				c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{
   502  					PassphraseTries: 1,
   503  					// activation with recovery key was disabled
   504  					RecoveryKeyTries: 0,
   505  					KeyringPrefix:    "ubuntu-fde",
   506  				})
   507  			}
   508  			if !tc.activated && tc.activateErr == nil {
   509  				return false, errors.New("activation error")
   510  			}
   511  			return tc.activated, tc.activateErr
   512  		})
   513  		defer restore()
   514  
   515  		restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, keyReader io.Reader,
   516  			options *sb.ActivateVolumeOptions) error {
   517  			if !tc.rkAllow {
   518  				c.Fatalf("unexpected attempt to activate with recovery key")
   519  				return fmt.Errorf("unexpected call")
   520  			}
   521  			return tc.rkErr
   522  		})
   523  		defer restore()
   524  
   525  		opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
   526  			AllowRecoveryKey: tc.rkAllow,
   527  		}
   528  		unlockRes, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(tc.disk, defaultDevice, expKeyPath, opts)
   529  		if tc.err == "" {
   530  			c.Assert(err, IsNil)
   531  			c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev)
   532  			c.Assert(unlockRes.PartDevice, Equals, devicePath)
   533  			if tc.hasEncdev {
   534  				c.Assert(unlockRes.FsDevice, Equals, filepath.Join("/dev/mapper", defaultDevice+"-"+randomUUID))
   535  			} else {
   536  				c.Assert(unlockRes.FsDevice, Equals, devicePath)
   537  			}
   538  		} else {
   539  			c.Assert(err, ErrorMatches, tc.err)
   540  			// also check that the IsEncrypted value matches, this is
   541  			// important for robust callers to know whether they should try to
   542  			// unlock using a different method or not
   543  			// this is only skipped on some test cases where we get an error
   544  			// very early, like trying to connect to the tpm
   545  			c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev)
   546  			if tc.hasEncdev {
   547  				c.Check(unlockRes.PartDevice, Equals, devicePath)
   548  				c.Check(unlockRes.FsDevice, Equals, "")
   549  			} else {
   550  				c.Check(unlockRes.PartDevice, Equals, "")
   551  				c.Check(unlockRes.FsDevice, Equals, "")
   552  			}
   553  		}
   554  
   555  		c.Assert(unlockRes.UnlockMethod, Equals, tc.expUnlockMethod)
   556  	}
   557  }
   558  
   559  func (s *secbootSuite) TestEFIImageFromBootFile(c *C) {
   560  	tmpDir := c.MkDir()
   561  
   562  	// set up some test files
   563  	existingFile := filepath.Join(tmpDir, "foo")
   564  	err := ioutil.WriteFile(existingFile, nil, 0644)
   565  	c.Assert(err, IsNil)
   566  	missingFile := filepath.Join(tmpDir, "bar")
   567  	snapFile := filepath.Join(tmpDir, "test.snap")
   568  	snapf, err := createMockSnapFile(c.MkDir(), snapFile, "app")
   569  
   570  	for _, tc := range []struct {
   571  		bootFile bootloader.BootFile
   572  		efiImage sb.EFIImage
   573  		err      string
   574  	}{
   575  		{
   576  			// happy case for EFI image
   577  			bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery),
   578  			efiImage: sb.FileEFIImage(existingFile),
   579  		},
   580  		{
   581  			// missing EFI image
   582  			bootFile: bootloader.NewBootFile("", missingFile, bootloader.RoleRecovery),
   583  			err:      fmt.Sprintf("file %s/bar does not exist", tmpDir),
   584  		},
   585  		{
   586  			// happy case for snap file
   587  			bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery),
   588  			efiImage: sb.SnapFileEFIImage{Container: snapf, FileName: "rel"},
   589  		},
   590  		{
   591  			// invalid snap file
   592  			bootFile: bootloader.NewBootFile(existingFile, "rel", bootloader.RoleRecovery),
   593  			err:      fmt.Sprintf(`"%s/foo" is not a snap or snapdir`, tmpDir),
   594  		},
   595  		{
   596  			// missing snap file
   597  			bootFile: bootloader.NewBootFile(missingFile, "rel", bootloader.RoleRecovery),
   598  			err:      fmt.Sprintf(`"%s/bar" is not a snap or snapdir`, tmpDir),
   599  		},
   600  	} {
   601  		o, err := secboot.EFIImageFromBootFile(&tc.bootFile)
   602  		if tc.err == "" {
   603  			c.Assert(err, IsNil)
   604  			c.Assert(o, DeepEquals, tc.efiImage)
   605  		} else {
   606  			c.Assert(err, ErrorMatches, tc.err)
   607  		}
   608  	}
   609  }
   610  
   611  func (s *secbootSuite) TestSealKey(c *C) {
   612  	mockErr := errors.New("some error")
   613  
   614  	for _, tc := range []struct {
   615  		tpmErr               error
   616  		tpmEnabled           bool
   617  		missingFile          bool
   618  		badSnapFile          bool
   619  		skipProvision        bool
   620  		addEFISbPolicyErr    error
   621  		addEFIBootManagerErr error
   622  		addSystemdEFIStubErr error
   623  		addSnapModelErr      error
   624  		provisioningErr      error
   625  		sealErr              error
   626  		provisioningCalls    int
   627  		sealCalls            int
   628  		expectedErr          string
   629  	}{
   630  		{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
   631  		{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
   632  		{tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file /does/not/exist does not exist"},
   633  		{tpmEnabled: true, badSnapFile: true, expectedErr: `.*/kernel.snap" is not a snap or snapdir`},
   634  		{tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"},
   635  		{tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"},
   636  		{tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
   637  		{tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
   638  		{tpmEnabled: true, provisioningErr: mockErr, provisioningCalls: 1, expectedErr: "cannot provision TPM: some error"},
   639  		{tpmEnabled: true, sealErr: mockErr, provisioningCalls: 1, sealCalls: 1, expectedErr: "some error"},
   640  		{tpmEnabled: true, skipProvision: true, provisioningCalls: 0, sealCalls: 1, expectedErr: ""},
   641  		{tpmEnabled: true, provisioningCalls: 1, sealCalls: 1, expectedErr: ""},
   642  	} {
   643  		tmpDir := c.MkDir()
   644  		var mockBF []bootloader.BootFile
   645  		for _, name := range []string{"a", "b", "c", "d"} {
   646  			mockFileName := filepath.Join(tmpDir, name)
   647  			err := ioutil.WriteFile(mockFileName, nil, 0644)
   648  			c.Assert(err, IsNil)
   649  			mockBF = append(mockBF, bootloader.NewBootFile("", mockFileName, bootloader.RoleRecovery))
   650  		}
   651  
   652  		if tc.missingFile {
   653  			mockBF[0].Path = "/does/not/exist"
   654  		}
   655  
   656  		var kernelSnap snap.Container
   657  		snapPath := filepath.Join(tmpDir, "kernel.snap")
   658  		if tc.badSnapFile {
   659  			err := ioutil.WriteFile(snapPath, nil, 0644)
   660  			c.Assert(err, IsNil)
   661  		} else {
   662  			var err error
   663  			kernelSnap, err = createMockSnapFile(c.MkDir(), snapPath, "kernel")
   664  			c.Assert(err, IsNil)
   665  		}
   666  
   667  		mockBF = append(mockBF, bootloader.NewBootFile(snapPath, "kernel.efi", bootloader.RoleRecovery))
   668  
   669  		myAuthKey := &ecdsa.PrivateKey{}
   670  
   671  		myParams := secboot.SealKeysParams{
   672  			ModelParams: []*secboot.SealKeyModelParams{
   673  				{
   674  					EFILoadChains: []*secboot.LoadChain{
   675  						secboot.NewLoadChain(mockBF[0],
   676  							secboot.NewLoadChain(mockBF[4])),
   677  					},
   678  					KernelCmdlines: []string{"cmdline1"},
   679  					Model:          &asserts.Model{},
   680  				},
   681  				{
   682  					EFILoadChains: []*secboot.LoadChain{
   683  						secboot.NewLoadChain(mockBF[0],
   684  							secboot.NewLoadChain(mockBF[2],
   685  								secboot.NewLoadChain(mockBF[4])),
   686  							secboot.NewLoadChain(mockBF[3],
   687  								secboot.NewLoadChain(mockBF[4]))),
   688  						secboot.NewLoadChain(mockBF[1],
   689  							secboot.NewLoadChain(mockBF[2],
   690  								secboot.NewLoadChain(mockBF[4])),
   691  							secboot.NewLoadChain(mockBF[3],
   692  								secboot.NewLoadChain(mockBF[4]))),
   693  					},
   694  					KernelCmdlines: []string{"cmdline2", "cmdline3"},
   695  					Model:          &asserts.Model{},
   696  				},
   697  			},
   698  			TPMPolicyAuthKey:       myAuthKey,
   699  			TPMPolicyAuthKeyFile:   filepath.Join(tmpDir, "policy-auth-key-file"),
   700  			TPMLockoutAuthFile:     filepath.Join(tmpDir, "lockout-auth-file"),
   701  			TPMProvision:           !tc.skipProvision,
   702  			PCRPolicyCounterHandle: 42,
   703  		}
   704  
   705  		myKey := secboot.EncryptionKey{}
   706  		myKey2 := secboot.EncryptionKey{}
   707  		for i := range myKey {
   708  			myKey[i] = byte(i)
   709  			myKey2[i] = byte(128 + i)
   710  		}
   711  
   712  		myKeys := []secboot.SealKeyRequest{
   713  			{
   714  				Key:     myKey,
   715  				KeyFile: "keyfile",
   716  			},
   717  			{
   718  				Key:     myKey2,
   719  				KeyFile: "keyfile2",
   720  			},
   721  		}
   722  
   723  		// events for
   724  		// a -> kernel
   725  		sequences1 := []*sb.EFIImageLoadEvent{
   726  			{
   727  				Source: sb.Firmware,
   728  				Image:  sb.FileEFIImage(mockBF[0].Path),
   729  				Next: []*sb.EFIImageLoadEvent{
   730  					{
   731  						Source: sb.Shim,
   732  						Image: sb.SnapFileEFIImage{
   733  							Container: kernelSnap,
   734  							FileName:  "kernel.efi",
   735  						},
   736  					},
   737  				},
   738  			},
   739  		}
   740  
   741  		// "cdk" events for
   742  		// c -> kernel OR
   743  		// d -> kernel
   744  		cdk := []*sb.EFIImageLoadEvent{
   745  			{
   746  				Source: sb.Shim,
   747  				Image:  sb.FileEFIImage(mockBF[2].Path),
   748  				Next: []*sb.EFIImageLoadEvent{
   749  					{
   750  						Source: sb.Shim,
   751  						Image: sb.SnapFileEFIImage{
   752  							Container: kernelSnap,
   753  							FileName:  "kernel.efi",
   754  						},
   755  					},
   756  				},
   757  			},
   758  			{
   759  				Source: sb.Shim,
   760  				Image:  sb.FileEFIImage(mockBF[3].Path),
   761  				Next: []*sb.EFIImageLoadEvent{
   762  					{
   763  						Source: sb.Shim,
   764  						Image: sb.SnapFileEFIImage{
   765  							Container: kernelSnap,
   766  							FileName:  "kernel.efi",
   767  						},
   768  					},
   769  				},
   770  			},
   771  		}
   772  
   773  		// events for
   774  		// a -> "cdk"
   775  		// b -> "cdk"
   776  		sequences2 := []*sb.EFIImageLoadEvent{
   777  			{
   778  				Source: sb.Firmware,
   779  				Image:  sb.FileEFIImage(mockBF[0].Path),
   780  				Next:   cdk,
   781  			},
   782  			{
   783  				Source: sb.Firmware,
   784  				Image:  sb.FileEFIImage(mockBF[1].Path),
   785  				Next:   cdk,
   786  			},
   787  		}
   788  
   789  		tpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   790  		defer restore()
   791  
   792  		// mock adding EFI secure boot policy profile
   793  		var pcrProfile *sb.PCRProtectionProfile
   794  		addEFISbPolicyCalls := 0
   795  		restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error {
   796  			addEFISbPolicyCalls++
   797  			pcrProfile = profile
   798  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   799  			switch addEFISbPolicyCalls {
   800  			case 1:
   801  				c.Assert(params.LoadSequences, DeepEquals, sequences1)
   802  			case 2:
   803  				c.Assert(params.LoadSequences, DeepEquals, sequences2)
   804  			default:
   805  				c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time")
   806  			}
   807  			return tc.addEFISbPolicyErr
   808  		})
   809  		defer restore()
   810  
   811  		// mock adding EFI boot manager profile
   812  		addEFIBootManagerCalls := 0
   813  		restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error {
   814  			addEFIBootManagerCalls++
   815  			c.Assert(profile, Equals, pcrProfile)
   816  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   817  			switch addEFISbPolicyCalls {
   818  			case 1:
   819  				c.Assert(params.LoadSequences, DeepEquals, sequences1)
   820  			case 2:
   821  				c.Assert(params.LoadSequences, DeepEquals, sequences2)
   822  			default:
   823  				c.Error("AddEFIBootManagerProfile shouldn't be called a third time")
   824  			}
   825  			return tc.addEFIBootManagerErr
   826  		})
   827  		defer restore()
   828  
   829  		// mock adding systemd EFI stub profile
   830  		addSystemdEfiStubCalls := 0
   831  		restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error {
   832  			addSystemdEfiStubCalls++
   833  			c.Assert(profile, Equals, pcrProfile)
   834  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   835  			c.Assert(params.PCRIndex, Equals, 12)
   836  			switch addSystemdEfiStubCalls {
   837  			case 1:
   838  				c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines)
   839  			case 2:
   840  				c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines)
   841  			default:
   842  				c.Error("AddSystemdEFIStubProfile shouldn't be called a third time")
   843  			}
   844  			return tc.addSystemdEFIStubErr
   845  		})
   846  		defer restore()
   847  
   848  		// mock adding snap model profile
   849  		addSnapModelCalls := 0
   850  		restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error {
   851  			addSnapModelCalls++
   852  			c.Assert(profile, Equals, pcrProfile)
   853  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   854  			c.Assert(params.PCRIndex, Equals, 12)
   855  			switch addSnapModelCalls {
   856  			case 1:
   857  				c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model)
   858  			case 2:
   859  				c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[1].Model)
   860  			default:
   861  				c.Error("AddSnapModelProfile shouldn't be called a third time")
   862  			}
   863  			return tc.addSnapModelErr
   864  		})
   865  		defer restore()
   866  
   867  		// mock provisioning
   868  		provisioningCalls := 0
   869  		restore = secboot.MockProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error {
   870  			provisioningCalls++
   871  			c.Assert(t, Equals, tpm)
   872  			c.Assert(mode, Equals, sb.ProvisionModeFull)
   873  			c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent)
   874  			return tc.provisioningErr
   875  		})
   876  		defer restore()
   877  
   878  		// mock sealing
   879  		sealCalls := 0
   880  		restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb.TPMConnection, kr []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error) {
   881  			sealCalls++
   882  			c.Assert(t, Equals, tpm)
   883  			c.Assert(kr, DeepEquals, []*sb.SealKeyRequest{{Key: myKey[:], Path: "keyfile"}, {Key: myKey2[:], Path: "keyfile2"}})
   884  			c.Assert(params.AuthKey, Equals, myAuthKey)
   885  			c.Assert(params.PCRPolicyCounterHandle, Equals, tpm2.Handle(42))
   886  			return sb.TPMPolicyAuthKey{}, tc.sealErr
   887  		})
   888  		defer restore()
   889  
   890  		// mock TPM enabled check
   891  		restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool {
   892  			return tc.tpmEnabled
   893  		})
   894  		defer restore()
   895  
   896  		err := secboot.SealKeys(myKeys, &myParams)
   897  		if tc.expectedErr == "" {
   898  			c.Assert(err, IsNil)
   899  			c.Assert(addEFISbPolicyCalls, Equals, 2)
   900  			c.Assert(addSystemdEfiStubCalls, Equals, 2)
   901  			c.Assert(addSnapModelCalls, Equals, 2)
   902  			c.Assert(osutil.FileExists(myParams.TPMPolicyAuthKeyFile), Equals, true)
   903  		} else {
   904  			c.Assert(err, ErrorMatches, tc.expectedErr)
   905  		}
   906  		c.Assert(provisioningCalls, Equals, tc.provisioningCalls)
   907  		c.Assert(sealCalls, Equals, tc.sealCalls)
   908  
   909  	}
   910  }
   911  
   912  func (s *secbootSuite) TestResealKey(c *C) {
   913  	mockErr := errors.New("some error")
   914  
   915  	for _, tc := range []struct {
   916  		tpmErr               error
   917  		tpmEnabled           bool
   918  		missingFile          bool
   919  		addEFISbPolicyErr    error
   920  		addEFIBootManagerErr error
   921  		addSystemdEFIStubErr error
   922  		addSnapModelErr      error
   923  		provisioningErr      error
   924  		resealErr            error
   925  		resealCalls          int
   926  		expectedErr          string
   927  	}{
   928  		{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
   929  		{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
   930  		{tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file .*/file.efi does not exist"},
   931  		{tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"},
   932  		{tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"},
   933  		{tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
   934  		{tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
   935  		{tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "some error"},
   936  		{tpmEnabled: true, resealCalls: 1, expectedErr: ""},
   937  	} {
   938  		mockTPMPolicyAuthKey := []byte{1, 3, 3, 7}
   939  		mockTPMPolicyAuthKeyFile := filepath.Join(c.MkDir(), "policy-auth-key-file")
   940  		err := ioutil.WriteFile(mockTPMPolicyAuthKeyFile, mockTPMPolicyAuthKey, 0600)
   941  		c.Assert(err, IsNil)
   942  
   943  		mockEFI := bootloader.NewBootFile("", filepath.Join(c.MkDir(), "file.efi"), bootloader.RoleRecovery)
   944  		if !tc.missingFile {
   945  			err := ioutil.WriteFile(mockEFI.Path, nil, 0644)
   946  			c.Assert(err, IsNil)
   947  		}
   948  
   949  		myParams := &secboot.ResealKeysParams{
   950  			ModelParams: []*secboot.SealKeyModelParams{
   951  				{
   952  					EFILoadChains:  []*secboot.LoadChain{secboot.NewLoadChain(mockEFI)},
   953  					KernelCmdlines: []string{"cmdline"},
   954  					Model:          &asserts.Model{},
   955  				},
   956  			},
   957  			KeyFiles:             []string{"keyfile", "keyfile2"},
   958  			TPMPolicyAuthKeyFile: mockTPMPolicyAuthKeyFile,
   959  		}
   960  
   961  		sequences := []*sb.EFIImageLoadEvent{
   962  			{
   963  				Source: sb.Firmware,
   964  				Image:  sb.FileEFIImage(mockEFI.Path),
   965  			},
   966  		}
   967  
   968  		// mock TPM connection
   969  		tpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   970  		defer restore()
   971  
   972  		// mock TPM enabled check
   973  		restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool {
   974  			return tc.tpmEnabled
   975  		})
   976  		defer restore()
   977  
   978  		// mock adding EFI secure boot policy profile
   979  		var pcrProfile *sb.PCRProtectionProfile
   980  		addEFISbPolicyCalls := 0
   981  		restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error {
   982  			addEFISbPolicyCalls++
   983  			pcrProfile = profile
   984  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   985  			c.Assert(params.LoadSequences, DeepEquals, sequences)
   986  			return tc.addEFISbPolicyErr
   987  		})
   988  		defer restore()
   989  
   990  		// mock adding EFI boot manager profile
   991  		addEFIBootManagerCalls := 0
   992  		restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error {
   993  			addEFIBootManagerCalls++
   994  			c.Assert(profile, Equals, pcrProfile)
   995  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   996  			c.Assert(params.LoadSequences, DeepEquals, sequences)
   997  			return tc.addEFIBootManagerErr
   998  		})
   999  		defer restore()
  1000  
  1001  		// mock adding systemd EFI stub profile
  1002  		addSystemdEfiStubCalls := 0
  1003  		restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error {
  1004  			addSystemdEfiStubCalls++
  1005  			c.Assert(profile, Equals, pcrProfile)
  1006  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
  1007  			c.Assert(params.PCRIndex, Equals, 12)
  1008  			c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines)
  1009  			return tc.addSystemdEFIStubErr
  1010  		})
  1011  		defer restore()
  1012  
  1013  		// mock adding snap model profile
  1014  		addSnapModelCalls := 0
  1015  		restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error {
  1016  			addSnapModelCalls++
  1017  			c.Assert(profile, Equals, pcrProfile)
  1018  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
  1019  			c.Assert(params.PCRIndex, Equals, 12)
  1020  			c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model)
  1021  			return tc.addSnapModelErr
  1022  		})
  1023  		defer restore()
  1024  
  1025  		// mock PCR protection policy update
  1026  		resealCalls := 0
  1027  		restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, profile *sb.PCRProtectionProfile) error {
  1028  			resealCalls++
  1029  			c.Assert(t, Equals, tpm)
  1030  			c.Assert(keyPaths, DeepEquals, []string{"keyfile", "keyfile2"})
  1031  			c.Assert(authKey, DeepEquals, sb.TPMPolicyAuthKey(mockTPMPolicyAuthKey))
  1032  			c.Assert(profile, Equals, pcrProfile)
  1033  			return tc.resealErr
  1034  		})
  1035  		defer restore()
  1036  
  1037  		err = secboot.ResealKeys(myParams)
  1038  		if tc.expectedErr == "" {
  1039  			c.Assert(err, IsNil)
  1040  			c.Assert(addEFISbPolicyCalls, Equals, 1)
  1041  			c.Assert(addSystemdEfiStubCalls, Equals, 1)
  1042  			c.Assert(addSnapModelCalls, Equals, 1)
  1043  		} else {
  1044  			c.Assert(err, ErrorMatches, tc.expectedErr)
  1045  		}
  1046  		c.Assert(resealCalls, Equals, tc.resealCalls)
  1047  	}
  1048  }
  1049  
  1050  func (s *secbootSuite) TestSealKeyNoModelParams(c *C) {
  1051  	myKeys := []secboot.SealKeyRequest{
  1052  		{
  1053  			Key:     secboot.EncryptionKey{},
  1054  			KeyFile: "keyfile",
  1055  		},
  1056  	}
  1057  	myParams := secboot.SealKeysParams{
  1058  		TPMPolicyAuthKeyFile: "policy-auth-key-file",
  1059  		TPMLockoutAuthFile:   "lockout-auth-file",
  1060  	}
  1061  
  1062  	err := secboot.SealKeys(myKeys, &myParams)
  1063  	c.Assert(err, ErrorMatches, "at least one set of model-specific parameters is required")
  1064  }
  1065  
  1066  func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, error) {
  1067  	snapYamlPath := filepath.Join(snapDir, "meta/snap.yaml")
  1068  	if err := os.MkdirAll(filepath.Dir(snapYamlPath), 0755); err != nil {
  1069  		return nil, err
  1070  	}
  1071  	if err := ioutil.WriteFile(snapYamlPath, []byte("name: foo"), 0644); err != nil {
  1072  		return nil, err
  1073  	}
  1074  	sqfs := squashfs.New(snapPath)
  1075  	if err := sqfs.Build(snapDir, &squashfs.BuildOpts{SnapType: snapType}); err != nil {
  1076  		return nil, err
  1077  	}
  1078  	return snapfile.Open(snapPath)
  1079  }
  1080  
  1081  func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) {
  1082  	tcti, err := tpm2.OpenTPMDevice("/dev/null")
  1083  	c.Assert(err, IsNil)
  1084  	tpmctx, err := tpm2.NewTPMContext(tcti)
  1085  	c.Assert(err, IsNil)
  1086  	tpm := &sb.TPMConnection{TPMContext: tpmctx}
  1087  	restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) {
  1088  		if tpmErr != nil {
  1089  			return nil, tpmErr
  1090  		}
  1091  		return tpm, nil
  1092  	})
  1093  	return tpm, restore
  1094  }
  1095  
  1096  func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) {
  1097  	disk := &disks.MockDiskMapping{
  1098  		FilesystemLabelToPartUUID: map[string]string{},
  1099  	}
  1100  	unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
  1101  	c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`)
  1102  	c.Check(unlockRes, DeepEquals, secboot.UnlockResult{})
  1103  }
  1104  
  1105  func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) {
  1106  	disk := &disks.MockDiskMapping{
  1107  		FilesystemLabelToPartUUID: map[string]string{
  1108  			"ubuntu-save-enc": "123-123-123",
  1109  		},
  1110  	}
  1111  	restore := secboot.MockRandomKernelUUID(func() string {
  1112  		return "random-uuid-123-123"
  1113  	})
  1114  	defer restore()
  1115  	restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte,
  1116  		options *sb.ActivateVolumeOptions) error {
  1117  		c.Check(options, DeepEquals, &sb.ActivateVolumeOptions{})
  1118  		c.Check(key, DeepEquals, []byte("fooo"))
  1119  		c.Check(volumeName, Matches, "ubuntu-save-random-uuid-123-123")
  1120  		c.Check(sourceDevicePath, Equals, "/dev/disk/by-partuuid/123-123-123")
  1121  		return nil
  1122  	})
  1123  	defer restore()
  1124  	unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
  1125  	c.Assert(err, IsNil)
  1126  	c.Check(unlockRes, DeepEquals, secboot.UnlockResult{
  1127  		PartDevice:   "/dev/disk/by-partuuid/123-123-123",
  1128  		FsDevice:     "/dev/mapper/ubuntu-save-random-uuid-123-123",
  1129  		IsEncrypted:  true,
  1130  		UnlockMethod: secboot.UnlockedWithKey,
  1131  	})
  1132  }
  1133  
  1134  func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) {
  1135  	disk := &disks.MockDiskMapping{
  1136  		FilesystemLabelToPartUUID: map[string]string{
  1137  			"ubuntu-save-enc": "123-123-123",
  1138  		},
  1139  	}
  1140  	restore := secboot.MockRandomKernelUUID(func() string {
  1141  		return "random-uuid-123-123"
  1142  	})
  1143  	defer restore()
  1144  	restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte,
  1145  		options *sb.ActivateVolumeOptions) error {
  1146  		return fmt.Errorf("failed")
  1147  	})
  1148  	defer restore()
  1149  	unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
  1150  	c.Assert(err, ErrorMatches, "failed")
  1151  	// we would have at least identified that the device is a decrypted one
  1152  	c.Check(unlockRes, DeepEquals, secboot.UnlockResult{
  1153  		IsEncrypted: true,
  1154  		PartDevice:  "/dev/disk/by-partuuid/123-123-123",
  1155  		FsDevice:    "",
  1156  	})
  1157  }
  1158  
  1159  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyTruncatesStreamFiles(c *C) {
  1160  	// this test uses a real systemd-run --user so check here if that
  1161  	// actually works
  1162  	if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "true").CombinedOutput(); err != nil {
  1163  		c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err)))
  1164  	}
  1165  
  1166  	// create the temporary output file streams with garbage data to ensure that
  1167  	// by the time the hook runs the files are emptied and recreated with the
  1168  	// right permissions
  1169  	streamFiles := []string{}
  1170  	for _, stream := range []string{"stdin", "stdout", "stderr"} {
  1171  		streamFile := filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key/fde-reveal-key."+stream)
  1172  		streamFiles = append(streamFiles, streamFile)
  1173  		// make the dir 0700
  1174  		err := os.MkdirAll(filepath.Dir(streamFile), 0700)
  1175  		c.Assert(err, IsNil)
  1176  		// but make the file world-readable as it should be reset to 0600 before
  1177  		// the hook is run
  1178  		err = ioutil.WriteFile(streamFile, []byte("blah blah blah blah blah blah blah blah blah blah"), 0755)
  1179  		c.Assert(err, IsNil)
  1180  	}
  1181  
  1182  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1183  		return true
  1184  	})
  1185  	defer restore()
  1186  
  1187  	// the hook script only verifies that the stdout file is empty since we
  1188  	// need to write to the stderr file for performing the test, but we still
  1189  	// check the stderr file for correct permissions
  1190  	mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", fmt.Sprintf(`
  1191  # check that stdin has the right sealed key content 
  1192  if [ "$(cat %[1]s)" != "{\"op\":\"reveal\",\"sealed-key\":\"AQIDBA==\",\"key-name\":\"name\"}" ]; then
  1193  	echo "test failed: stdin file has wrong content: $(cat %[1]s)" 1>&2
  1194  else
  1195  	echo "stdin file has correct content" 1>&2
  1196  fi
  1197  
  1198  # check that stdout is empty
  1199  if [ -n "$(cat %[2]s)" ]; then
  1200  	echo "test failed: stdout file is not empty: $(cat %[2]s)" 1>&2
  1201  else
  1202  	echo "stdout file is correctly empty" 1>&2
  1203  fi
  1204  
  1205  # check that stdin has the right 600 perms
  1206  if [ "$(stat --format=%%a %[1]s)" != "600" ]; then
  1207  	echo "test failed: stdin file has wrong permissions: $(stat --format=%%a %[1]s)" 1>&2
  1208  else 
  1209  	echo "stdin file has correct 600 permissions" 1>&2
  1210  fi
  1211  
  1212  # check that stdout has the right 600 perms
  1213  if [ "$(stat --format=%%a %[2]s)" != "600" ]; then
  1214  	echo "test failed: stdout file has wrong permissions: $(stat --format=%%a %[2]s)" 1>&2
  1215  else 
  1216  	echo "stdout file has correct 600 permissions" 1>&2
  1217  fi
  1218  
  1219  # check that stderr has the right 600 perms
  1220  if [ "$(stat --format=%%a %[3]s)" != "600" ]; then
  1221  	echo "test failed: stderr file has wrong permissions: $(stat --format=%%a %[3]s)" 1>&2
  1222  else 
  1223  	echo "stderr file has correct 600 permissions" 1>&2
  1224  fi
  1225  
  1226  echo "making the hook always fail for simpler test code" 1>&2
  1227  
  1228  # always make the hook exit 1 for simpler test code
  1229  exit 1
  1230  `, streamFiles[0], streamFiles[1], streamFiles[2]))
  1231  	defer mockSystemdRun.Restore()
  1232  	restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"})
  1233  	defer restore()
  1234  
  1235  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1236  		FilesystemLabelToPartUUID: map[string]string{
  1237  			"name-enc": "enc-dev-partuuid",
  1238  		},
  1239  	}
  1240  	defaultDevice := "name"
  1241  	mockSealedKeyFile := filepath.Join(c.MkDir(), "vanilla-keyfile")
  1242  	err := ioutil.WriteFile(mockSealedKeyFile, []byte{1, 2, 3, 4}, 0600)
  1243  	c.Assert(err, IsNil)
  1244  
  1245  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
  1246  	_, err = secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1247  	c.Assert(err, ErrorMatches, `(?s)cannot run fde-reveal-key: 
  1248  -----
  1249  stdin file has correct content
  1250  stdout file is correctly empty
  1251  stdin file has correct 600 permissions
  1252  stdout file has correct 600 permissions
  1253  stderr file has correct 600 permissions
  1254  making the hook always fail for simpler test code
  1255  service result: exit-code
  1256  -----`)
  1257  	// ensure no tmp files are left behind
  1258  	c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false)
  1259  }
  1260  
  1261  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr(c *C) {
  1262  	// this test uses a real systemd-run --user so check here if that
  1263  	// actually works
  1264  	if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil {
  1265  		c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err)))
  1266  	}
  1267  
  1268  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1269  		return true
  1270  	})
  1271  	defer restore()
  1272  
  1273  	mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", `echo failed 1>&2; false`)
  1274  	defer mockSystemdRun.Restore()
  1275  	restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"})
  1276  	defer restore()
  1277  
  1278  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1279  		FilesystemLabelToPartUUID: map[string]string{
  1280  			"name-enc": "enc-dev-partuuid",
  1281  		},
  1282  	}
  1283  	defaultDevice := "name"
  1284  	mockSealedKeyFile := filepath.Join(c.MkDir(), "vanilla-keyfile")
  1285  	err := ioutil.WriteFile(mockSealedKeyFile, []byte{1, 2, 3, 4}, 0600)
  1286  	c.Assert(err, IsNil)
  1287  
  1288  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
  1289  	_, err = secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1290  	c.Assert(err, ErrorMatches, `(?s)cannot run fde-reveal-key: 
  1291  -----
  1292  failed
  1293  service result: exit-code
  1294  -----`)
  1295  	// ensure no tmp files are left behind
  1296  	c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false)
  1297  }
  1298  
  1299  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKey(c *C) {
  1300  	// this test uses a real systemd-run --user so check here if that
  1301  	// actually works
  1302  	if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil {
  1303  		c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err)))
  1304  	}
  1305  
  1306  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1307  		return true
  1308  	})
  1309  	defer restore()
  1310  
  1311  	restore = secboot.MockRandomKernelUUID(func() string {
  1312  		return "random-uuid-for-test"
  1313  	})
  1314  	defer restore()
  1315  
  1316  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1317  		FilesystemLabelToPartUUID: map[string]string{
  1318  			"name-enc": "enc-dev-partuuid",
  1319  		},
  1320  	}
  1321  
  1322  	restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"})
  1323  	defer restore()
  1324  	fdeRevealKeyStdin := filepath.Join(c.MkDir(), "stdin")
  1325  	mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", fmt.Sprintf(`
  1326  cat - > %s
  1327  printf "unsealed-key-from-hook"
  1328  `, fdeRevealKeyStdin))
  1329  	defer mockSystemdRun.Restore()
  1330  
  1331  	restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, options *sb.ActivateVolumeOptions) error {
  1332  		c.Check(string(key), Equals, "unsealed-key-from-hook")
  1333  		return nil
  1334  	})
  1335  	defer restore()
  1336  
  1337  	defaultDevice := "name"
  1338  	mockSealedKeyFile := filepath.Join(c.MkDir(), "vanilla-keyfile")
  1339  	err := ioutil.WriteFile(mockSealedKeyFile, []byte("sealed-key"), 0600)
  1340  	c.Assert(err, IsNil)
  1341  
  1342  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
  1343  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1344  	c.Assert(err, IsNil)
  1345  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1346  		UnlockMethod: secboot.UnlockedWithSealedKey,
  1347  		IsEncrypted:  true,
  1348  		PartDevice:   "/dev/disk/by-partuuid/enc-dev-partuuid",
  1349  		FsDevice:     "/dev/mapper/name-random-uuid-for-test",
  1350  	})
  1351  	c.Check(mockSystemdRun.Calls(), DeepEquals, [][]string{
  1352  		{"fde-reveal-key"},
  1353  	})
  1354  	c.Check(fdeRevealKeyStdin, testutil.FileEquals, fmt.Sprintf(`{"op":"reveal","sealed-key":%q,"key-name":"name"}`, base64.StdEncoding.EncodeToString([]byte("sealed-key"))))
  1355  
  1356  	// ensure no tmp files are left behind
  1357  	c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false)
  1358  }
  1359  
  1360  func (s *secbootSuite) TestLockSealedKeysCallsFdeReveal(c *C) {
  1361  	// this test uses a real systemd-run --user so check here if that
  1362  	// actually works
  1363  	if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil {
  1364  		c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err)))
  1365  	}
  1366  
  1367  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1368  		return true
  1369  	})
  1370  	defer restore()
  1371  
  1372  	restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"})
  1373  	defer restore()
  1374  	fdeRevealKeyStdin := filepath.Join(c.MkDir(), "stdin")
  1375  	mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", fmt.Sprintf(`
  1376  cat - > %s
  1377  `, fdeRevealKeyStdin))
  1378  	defer mockSystemdRun.Restore()
  1379  
  1380  	err := secboot.LockSealedKeys()
  1381  	c.Assert(err, IsNil)
  1382  	c.Check(mockSystemdRun.Calls(), DeepEquals, [][]string{
  1383  		{"fde-reveal-key"},
  1384  	})
  1385  	c.Check(fdeRevealKeyStdin, testutil.FileEquals, `{"op":"lock"}`)
  1386  
  1387  	// ensure no tmp files are left behind
  1388  	c.Check(osutil.FileExists(filepath.Join(dirs.GlobalRootDir, "/run/fde-reveal-key")), Equals, false)
  1389  }
  1390  
  1391  func (s *secbootSuite) TestLockSealedKeysHonorsRuntimeMax(c *C) {
  1392  	// this test uses a real systemd-run --user so check here if that
  1393  	// actually works
  1394  	if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil {
  1395  		c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err)))
  1396  	}
  1397  
  1398  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1399  		return true
  1400  	})
  1401  	defer restore()
  1402  
  1403  	restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"})
  1404  	defer restore()
  1405  	mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", "sleep 60")
  1406  	defer mockSystemdRun.Restore()
  1407  
  1408  	restore = secboot.MockFdeRevealKeyPollWaitParanoiaFactor(100)
  1409  	defer restore()
  1410  
  1411  	restore = secboot.MockFdeRevealKeyRuntimeMax(100 * time.Millisecond)
  1412  	defer restore()
  1413  
  1414  	err := secboot.LockSealedKeys()
  1415  	c.Assert(err, ErrorMatches, `cannot run fde-reveal-key "lock": service result: timeout`)
  1416  }
  1417  
  1418  func (s *secbootSuite) TestLockSealedKeysHonorsParanoia(c *C) {
  1419  	// this test uses a real systemd-run --user so check here if that
  1420  	// actually works
  1421  	if output, err := exec.Command("systemd-run", "--user", "--wait", "--collect", "--service-type=exec", "/bin/true").CombinedOutput(); err != nil {
  1422  		c.Skip(fmt.Sprintf("systemd-run not working: %v", osutil.OutputErr(output, err)))
  1423  	}
  1424  
  1425  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1426  		return true
  1427  	})
  1428  	defer restore()
  1429  
  1430  	restore = secboot.MockFdeRevealKeyCommandExtra([]string{"--user"})
  1431  	defer restore()
  1432  	mockSystemdRun := testutil.MockCommand(c, "fde-reveal-key", "sleep 60")
  1433  	defer mockSystemdRun.Restore()
  1434  
  1435  	restore = secboot.MockFdeRevealKeyPollWaitParanoiaFactor(1)
  1436  	defer restore()
  1437  
  1438  	// shorter than the fdeRevealKeyPollWait time
  1439  	restore = secboot.MockFdeRevealKeyRuntimeMax(1 * time.Millisecond)
  1440  	defer restore()
  1441  
  1442  	err := secboot.LockSealedKeys()
  1443  	c.Assert(err, ErrorMatches, `cannot run fde-reveal-key "lock": internal error: systemd-run did not honor RuntimeMax=1ms setting`)
  1444  }