github.com/hugh712/snapd@v0.0.0-20200910133618-1a99902bd583/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  	"errors"
    25  	"fmt"
    26  	"io"
    27  	"io/ioutil"
    28  	"os"
    29  	"path/filepath"
    30  	"testing"
    31  
    32  	"github.com/canonical/go-tpm2"
    33  	sb "github.com/snapcore/secboot"
    34  	. "gopkg.in/check.v1"
    35  
    36  	"github.com/snapcore/snapd/asserts"
    37  	"github.com/snapcore/snapd/bootloader"
    38  	"github.com/snapcore/snapd/bootloader/efi"
    39  	"github.com/snapcore/snapd/dirs"
    40  	"github.com/snapcore/snapd/osutil/disks"
    41  	"github.com/snapcore/snapd/secboot"
    42  	"github.com/snapcore/snapd/snap"
    43  	"github.com/snapcore/snapd/snap/snapfile"
    44  	"github.com/snapcore/snapd/snap/squashfs"
    45  	"github.com/snapcore/snapd/testutil"
    46  )
    47  
    48  func TestSecboot(t *testing.T) { TestingT(t) }
    49  
    50  type secbootSuite struct {
    51  	testutil.BaseTest
    52  }
    53  
    54  var _ = Suite(&secbootSuite{})
    55  
    56  func (s *secbootSuite) SetUpTest(c *C) {
    57  	dirs.SetRootDir(c.MkDir())
    58  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    59  }
    60  
    61  func (s *secbootSuite) TestCheckKeySealingSupported(c *C) {
    62  	sbEmpty := []uint8{}
    63  	sbEnabled := []uint8{1}
    64  	sbDisabled := []uint8{0}
    65  	efiNotSupported := []uint8(nil)
    66  	tpmErr := errors.New("TPM error")
    67  
    68  	type testCase struct {
    69  		tpmErr     error
    70  		tpmEnabled bool
    71  		sbData     []uint8
    72  		err        string
    73  	}
    74  	for i, tc := range []testCase{
    75  		// happy case
    76  		{tpmErr: nil, tpmEnabled: true, sbData: sbEnabled, err: ""},
    77  		// secure boot EFI var is empty
    78  		{tpmErr: nil, tpmEnabled: true, sbData: sbEmpty, err: "secure boot variable does not exist"},
    79  		// secure boot is disabled
    80  		{tpmErr: nil, tpmEnabled: true, sbData: sbDisabled, err: "secure boot is disabled"},
    81  		// EFI not supported
    82  		{tpmErr: nil, tpmEnabled: true, sbData: efiNotSupported, err: "not a supported EFI system"},
    83  		// TPM connection error
    84  		{tpmErr: tpmErr, sbData: sbEnabled, err: "cannot connect to TPM device: TPM error"},
    85  		// TPM was detected but it's not enabled
    86  		{tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"},
    87  		// No TPM device
    88  		{tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"},
    89  	} {
    90  		c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err)
    91  
    92  		_, restore := mockSbTPMConnection(c, tc.tpmErr)
    93  		defer restore()
    94  
    95  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
    96  			return tc.tpmEnabled
    97  		})
    98  		defer restore()
    99  
   100  		var vars map[string][]byte
   101  		if tc.sbData != nil {
   102  			vars = map[string][]byte{"SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c": tc.sbData}
   103  		}
   104  		restoreEfiVars := efi.MockVars(vars, nil)
   105  		defer restoreEfiVars()
   106  
   107  		err := secboot.CheckKeySealingSupported()
   108  		if tc.err == "" {
   109  			c.Assert(err, IsNil)
   110  		} else {
   111  			c.Assert(err, ErrorMatches, tc.err)
   112  		}
   113  	}
   114  }
   115  
   116  func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) {
   117  	for _, tc := range []struct {
   118  		tpmErr     error
   119  		tpmEnabled bool
   120  		callNum    int
   121  		err        string
   122  	}{
   123  		{
   124  			// normal connection to the TPM device
   125  			tpmErr: nil, tpmEnabled: true, callNum: 1, err: "",
   126  		},
   127  		{
   128  			// TPM device exists but returns error
   129  			tpmErr: errors.New("tpm error"), callNum: 0,
   130  			err: "cannot measure snap system epoch: cannot open TPM connection: tpm error",
   131  		},
   132  		{
   133  			// TPM device exists but is disabled
   134  			tpmErr: nil, tpmEnabled: false,
   135  		},
   136  		{
   137  			// TPM device does not exist
   138  			tpmErr: sb.ErrNoTPM2Device,
   139  		},
   140  	} {
   141  		mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   142  		defer restore()
   143  
   144  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   145  			return tc.tpmEnabled
   146  		})
   147  		defer restore()
   148  
   149  		calls := 0
   150  		restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error {
   151  			calls++
   152  			c.Assert(tpm, Equals, mockTpm)
   153  			c.Assert(pcrIndex, Equals, 12)
   154  			return nil
   155  		})
   156  		defer restore()
   157  
   158  		err := secboot.MeasureSnapSystemEpochWhenPossible()
   159  		if tc.err == "" {
   160  			c.Assert(err, IsNil)
   161  		} else {
   162  			c.Assert(err, ErrorMatches, tc.err)
   163  		}
   164  		c.Assert(calls, Equals, tc.callNum)
   165  	}
   166  }
   167  
   168  func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) {
   169  	for i, tc := range []struct {
   170  		tpmErr     error
   171  		tpmEnabled bool
   172  		modelErr   error
   173  		callNum    int
   174  		err        string
   175  	}{
   176  		{
   177  			// normal connection to the TPM device
   178  			tpmErr: nil, tpmEnabled: true, modelErr: nil, callNum: 1, err: "",
   179  		},
   180  		{
   181  			// normal connection to the TPM device with model error
   182  			tpmErr: nil, tpmEnabled: true, modelErr: errors.New("model error"), callNum: 0,
   183  			err: "cannot measure snap model: model error",
   184  		},
   185  		{
   186  			// TPM device exists but returns error
   187  			tpmErr: errors.New("tpm error"), callNum: 0,
   188  			err: "cannot measure snap model: cannot open TPM connection: tpm error",
   189  		},
   190  		{
   191  			// TPM device exists but is disabled
   192  			tpmErr: nil, tpmEnabled: false,
   193  		},
   194  		{
   195  			// TPM device does not exist
   196  			tpmErr: sb.ErrNoTPM2Device,
   197  		},
   198  	} {
   199  		c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled)
   200  		mockModel := &asserts.Model{}
   201  
   202  		mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   203  		defer restore()
   204  
   205  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   206  			return tc.tpmEnabled
   207  		})
   208  		defer restore()
   209  
   210  		calls := 0
   211  		restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model *asserts.Model) error {
   212  			calls++
   213  			c.Assert(tpm, Equals, mockTpm)
   214  			c.Assert(model, Equals, mockModel)
   215  			c.Assert(pcrIndex, Equals, 12)
   216  			return nil
   217  		})
   218  		defer restore()
   219  
   220  		findModel := func() (*asserts.Model, error) {
   221  			if tc.modelErr != nil {
   222  				return nil, tc.modelErr
   223  			}
   224  			return mockModel, nil
   225  		}
   226  
   227  		err := secboot.MeasureSnapModelWhenPossible(findModel)
   228  		if tc.err == "" {
   229  			c.Assert(err, IsNil)
   230  		} else {
   231  			c.Assert(err, ErrorMatches, tc.err)
   232  		}
   233  		c.Assert(calls, Equals, tc.callNum)
   234  	}
   235  }
   236  
   237  func (s *secbootSuite) TestUnlockIfEncrypted(c *C) {
   238  
   239  	// setup mock disks to use for locating the partition
   240  	// restore := disks.MockMountPointDisksToPartitionMapping()
   241  	// defer restore()
   242  
   243  	mockDiskWithEncDev := &disks.MockDiskMapping{
   244  		FilesystemLabelToPartUUID: map[string]string{
   245  			"name-enc": "enc-dev-partuuid",
   246  		},
   247  	}
   248  
   249  	mockDiskWithoutAnyDev := &disks.MockDiskMapping{
   250  		FilesystemLabelToPartUUID: map[string]string{},
   251  	}
   252  
   253  	mockDiskWithUnencDev := &disks.MockDiskMapping{
   254  		FilesystemLabelToPartUUID: map[string]string{
   255  			"name": "unenc-dev-partuuid",
   256  		},
   257  	}
   258  
   259  	for idx, tc := range []struct {
   260  		tpmErr      error
   261  		tpmEnabled  bool  // TPM storage and endorsement hierarchies disabled, only relevant if TPM available
   262  		hasEncdev   bool  // an encrypted device exists
   263  		rkErr       error // recovery key unlock error, only relevant if TPM not available
   264  		lockRequest bool  // request to lock access to the sealed key, only relevant if TPM available
   265  		lockOk      bool  // the lock operation succeeded
   266  		activated   bool  // the activation operation succeeded
   267  		device      string
   268  		err         string
   269  		disk        *disks.MockDiskMapping
   270  	}{
   271  		{
   272  			// happy case with tpm and encrypted device (lock requested)
   273  			tpmEnabled: true, hasEncdev: true, lockRequest: true, lockOk: true,
   274  			activated: true, device: "name",
   275  			disk: mockDiskWithEncDev,
   276  		}, {
   277  			// device activation fails (lock requested)
   278  			tpmEnabled: true, hasEncdev: true, lockRequest: true, lockOk: true,
   279  			err:    "cannot activate encrypted device .*: activation error",
   280  			device: "name",
   281  			disk:   mockDiskWithEncDev,
   282  		}, {
   283  			// activation works but lock fails (lock requested)
   284  			tpmEnabled: true, hasEncdev: true, lockRequest: true, activated: true,
   285  			err:    "cannot lock access to sealed keys: lock failed",
   286  			device: "name",
   287  			disk:   mockDiskWithEncDev,
   288  		}, {
   289  			// happy case with tpm and encrypted device
   290  			tpmEnabled: true, hasEncdev: true, lockOk: true, activated: true,
   291  			device: "name",
   292  			disk:   mockDiskWithEncDev,
   293  		}, {
   294  			// device activation fails
   295  			tpmEnabled: true, hasEncdev: true,
   296  			err:    "cannot activate encrypted device .*: activation error",
   297  			device: "name",
   298  			disk:   mockDiskWithEncDev,
   299  		}, {
   300  			// activation works but lock fails
   301  			tpmEnabled: true, hasEncdev: true, activated: true, device: "name",
   302  			disk: mockDiskWithEncDev,
   303  		}, {
   304  			// happy case without encrypted device (lock requested)
   305  			tpmEnabled: true, lockRequest: true, lockOk: true, activated: true,
   306  			device: "name",
   307  			disk:   mockDiskWithUnencDev,
   308  		}, {
   309  			// activation works but lock fails, without encrypted device (lock requested)
   310  			tpmEnabled: true, lockRequest: true, activated: true,
   311  			err:  "cannot lock access to sealed keys: lock failed",
   312  			disk: mockDiskWithUnencDev,
   313  		}, {
   314  			// happy case without encrypted device
   315  			tpmEnabled: true, lockOk: true, activated: true, device: "name",
   316  			disk: mockDiskWithUnencDev,
   317  		}, {
   318  			// activation works but lock fails, no encrypted device
   319  			tpmEnabled: true, activated: true, device: "name",
   320  			disk: mockDiskWithUnencDev,
   321  		}, {
   322  			// tpm error, no encrypted device
   323  			tpmErr: errors.New("tpm error"),
   324  			err:    `cannot unlock encrypted device "name": tpm error`,
   325  			disk:   mockDiskWithUnencDev,
   326  		}, {
   327  			// tpm error, has encrypted device
   328  			tpmErr: errors.New("tpm error"), hasEncdev: true,
   329  			err:  `cannot unlock encrypted device "name": tpm error`,
   330  			disk: mockDiskWithEncDev,
   331  		}, {
   332  			// tpm disabled, no encrypted device
   333  			device: "name",
   334  			disk:   mockDiskWithUnencDev,
   335  		}, {
   336  			// tpm disabled, has encrypted device, unlocked using the recovery key
   337  			hasEncdev: true,
   338  			device:    "name",
   339  			disk:      mockDiskWithEncDev,
   340  		}, {
   341  			// tpm disabled, has encrypted device, recovery key unlocking fails
   342  			hasEncdev: true, rkErr: errors.New("cannot unlock with recovery key"),
   343  			disk: mockDiskWithEncDev,
   344  			err:  `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
   345  		}, {
   346  			// no tpm, has encrypted device, unlocked using the recovery key (lock requested)
   347  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, lockRequest: true,
   348  			disk:   mockDiskWithEncDev,
   349  			device: "name",
   350  		}, {
   351  			// no tpm, has encrypted device, recovery key unlocking fails
   352  			rkErr:  errors.New("cannot unlock with recovery key"),
   353  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true, lockRequest: true,
   354  			disk: mockDiskWithEncDev,
   355  			err:  `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
   356  		}, {
   357  			// no tpm, has encrypted device, unlocked using the recovery key
   358  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
   359  			disk:   mockDiskWithEncDev,
   360  			device: "name",
   361  		}, {
   362  			// no tpm, no encrypted device (lock requested)
   363  			tpmErr: sb.ErrNoTPM2Device, lockRequest: true,
   364  			disk:   mockDiskWithUnencDev,
   365  			device: "name",
   366  		}, {
   367  			// no tpm, no encrypted device
   368  			tpmErr: sb.ErrNoTPM2Device,
   369  			disk:   mockDiskWithUnencDev,
   370  			device: "name",
   371  		}, {
   372  			// no disks at all
   373  			disk:   mockDiskWithoutAnyDev,
   374  			device: "name",
   375  			// error is specifically for failing to find name, NOT name-enc, we
   376  			// will properly fall back to looking for name if we didn't find
   377  			// name-enc
   378  			err: "filesystem label \"name\" not found",
   379  		},
   380  	} {
   381  		randomUUID := fmt.Sprintf("random-uuid-for-test-%d", idx)
   382  		restore := secboot.MockRandomKernelUUID(func() string {
   383  			return randomUUID
   384  		})
   385  		defer restore()
   386  
   387  		c.Logf("tc %v: %+v", idx, tc)
   388  		mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr)
   389  		defer restoreConnect()
   390  
   391  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   392  			return tc.tpmEnabled
   393  		})
   394  		defer restore()
   395  
   396  		n := 0
   397  		restore = secboot.MockSbLockAccessToSealedKeys(func(tpm *sb.TPMConnection) error {
   398  			n++
   399  			c.Assert(tpm, Equals, mockSbTPM)
   400  			if tc.lockOk {
   401  				return nil
   402  			}
   403  			return errors.New("lock failed")
   404  		})
   405  		defer restore()
   406  
   407  		fsLabel := tc.device
   408  		if tc.hasEncdev {
   409  			fsLabel += "-enc"
   410  		}
   411  		partuuid := tc.disk.FilesystemLabelToPartUUID[fsLabel]
   412  		devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid)
   413  
   414  		restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath,
   415  			keyPath string, pinReader io.Reader, options *sb.ActivateWithTPMSealedKeyOptions) (bool, error) {
   416  			c.Assert(volumeName, Equals, "name-"+randomUUID)
   417  			c.Assert(sourceDevicePath, Equals, devicePath)
   418  			c.Assert(keyPath, Equals, filepath.Join("encrypt-key-dir", "name.sealed-key"))
   419  			c.Assert(*options, DeepEquals, sb.ActivateWithTPMSealedKeyOptions{
   420  				PINTries:            1,
   421  				RecoveryKeyTries:    3,
   422  				LockSealedKeyAccess: tc.lockRequest,
   423  			})
   424  			if !tc.activated {
   425  				return false, errors.New("activation error")
   426  			}
   427  			return true, nil
   428  		})
   429  		defer restore()
   430  
   431  		restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, keyReader io.Reader,
   432  			options *sb.ActivateWithRecoveryKeyOptions) error {
   433  			return tc.rkErr
   434  		})
   435  		defer restore()
   436  
   437  		device, isDecryptDev, err := secboot.UnlockVolumeIfEncrypted(tc.disk, "name", "encrypt-key-dir", tc.lockRequest)
   438  		if tc.err == "" {
   439  			c.Assert(err, IsNil)
   440  			c.Assert(isDecryptDev, Equals, tc.hasEncdev)
   441  			if tc.hasEncdev {
   442  				c.Assert(device, Equals, filepath.Join("/dev/mapper", tc.device+"-"+randomUUID))
   443  			} else {
   444  				c.Assert(device, Equals, devicePath)
   445  			}
   446  		} else {
   447  			c.Assert(err, ErrorMatches, tc.err)
   448  		}
   449  		// LockAccessToSealedKeys should be called whenever there is a TPM device
   450  		// detected, regardless of whether secure boot is enabled or there is an
   451  		// encrypted volume to unlock. If we have multiple encrypted volumes, we
   452  		// should call it after the last one is unlocked.
   453  		if tc.tpmErr == nil && tc.lockRequest {
   454  			c.Assert(n, Equals, 1)
   455  		} else {
   456  			c.Assert(n, Equals, 0)
   457  		}
   458  	}
   459  }
   460  
   461  func (s *secbootSuite) TestEFIImageFromBootFile(c *C) {
   462  	tmpDir := c.MkDir()
   463  
   464  	// set up some test files
   465  	existingFile := filepath.Join(tmpDir, "foo")
   466  	err := ioutil.WriteFile(existingFile, nil, 0644)
   467  	c.Assert(err, IsNil)
   468  	missingFile := filepath.Join(tmpDir, "bar")
   469  	snapFile := filepath.Join(tmpDir, "test.snap")
   470  	snapf, err := createMockSnapFile(c.MkDir(), snapFile, "app")
   471  
   472  	for _, tc := range []struct {
   473  		bootFile bootloader.BootFile
   474  		efiImage sb.EFIImage
   475  		err      string
   476  	}{
   477  		{
   478  			// happy case for EFI image
   479  			bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery),
   480  			efiImage: sb.FileEFIImage(existingFile),
   481  		},
   482  		{
   483  			// missing EFI image
   484  			bootFile: bootloader.NewBootFile("", missingFile, bootloader.RoleRecovery),
   485  			err:      fmt.Sprintf("file %s/bar does not exist", tmpDir),
   486  		},
   487  		{
   488  			// happy case for snap file
   489  			bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery),
   490  			efiImage: sb.SnapFileEFIImage{Container: snapf, Path: snapFile, FileName: "rel"},
   491  		},
   492  		{
   493  			// invalid snap file
   494  			bootFile: bootloader.NewBootFile(existingFile, "rel", bootloader.RoleRecovery),
   495  			err:      fmt.Sprintf(`"%s/foo" is not a snap or snapdir`, tmpDir),
   496  		},
   497  		{
   498  			// missing snap file
   499  			bootFile: bootloader.NewBootFile(missingFile, "rel", bootloader.RoleRecovery),
   500  			err:      fmt.Sprintf(`"%s/bar" is not a snap or snapdir`, tmpDir),
   501  		},
   502  	} {
   503  		o, err := secboot.EFIImageFromBootFile(&tc.bootFile)
   504  		if tc.err == "" {
   505  			c.Assert(err, IsNil)
   506  			c.Assert(o, DeepEquals, tc.efiImage)
   507  		} else {
   508  			c.Assert(err, ErrorMatches, tc.err)
   509  		}
   510  	}
   511  }
   512  
   513  func (s *secbootSuite) TestSealKey(c *C) {
   514  	mockErr := errors.New("some error")
   515  
   516  	for _, tc := range []struct {
   517  		tpmErr               error
   518  		tpmEnabled           bool
   519  		missingFile          bool
   520  		badSnapFile          bool
   521  		addEFISbPolicyErr    error
   522  		addSystemdEFIStubErr error
   523  		addSnapModelErr      error
   524  		provisioningErr      error
   525  		sealErr              error
   526  		provisioningCalls    int
   527  		sealCalls            int
   528  		expectedErr          string
   529  	}{
   530  		{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
   531  		{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
   532  		{tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file /does/not/exist does not exist"},
   533  		{tpmEnabled: true, badSnapFile: true, expectedErr: `.*/kernel.snap" is not a snap or snapdir`},
   534  		{tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"},
   535  		{tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
   536  		{tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
   537  		{tpmEnabled: true, provisioningErr: mockErr, provisioningCalls: 1, expectedErr: "cannot provision TPM: some error"},
   538  		{tpmEnabled: true, sealErr: mockErr, provisioningCalls: 1, sealCalls: 1, expectedErr: "some error"},
   539  		{tpmEnabled: true, provisioningCalls: 1, sealCalls: 1, expectedErr: ""},
   540  	} {
   541  		tmpDir := c.MkDir()
   542  		var mockBF []bootloader.BootFile
   543  		for _, name := range []string{"a", "b", "c", "d"} {
   544  			mockFileName := filepath.Join(tmpDir, name)
   545  			err := ioutil.WriteFile(mockFileName, nil, 0644)
   546  			c.Assert(err, IsNil)
   547  			mockBF = append(mockBF, bootloader.NewBootFile("", mockFileName, bootloader.RoleRecovery))
   548  		}
   549  
   550  		if tc.missingFile {
   551  			mockBF[0].Path = "/does/not/exist"
   552  		}
   553  
   554  		var kernelSnap snap.Container
   555  		snapPath := filepath.Join(tmpDir, "kernel.snap")
   556  		if tc.badSnapFile {
   557  			err := ioutil.WriteFile(snapPath, nil, 0644)
   558  			c.Assert(err, IsNil)
   559  		} else {
   560  			var err error
   561  			kernelSnap, err = createMockSnapFile(c.MkDir(), snapPath, "kernel")
   562  			c.Assert(err, IsNil)
   563  		}
   564  
   565  		mockBF = append(mockBF, bootloader.NewBootFile(snapPath, "kernel.efi", bootloader.RoleRecovery))
   566  
   567  		myParams := secboot.SealKeyParams{
   568  			ModelParams: []*secboot.SealKeyModelParams{
   569  				{
   570  					EFILoadChains: []*secboot.LoadChain{
   571  						secboot.NewLoadChain(mockBF[0],
   572  							secboot.NewLoadChain(mockBF[4])),
   573  					},
   574  					KernelCmdlines: []string{"cmdline1"},
   575  					Model:          &asserts.Model{},
   576  				},
   577  				{
   578  					EFILoadChains: []*secboot.LoadChain{
   579  						secboot.NewLoadChain(mockBF[0],
   580  							secboot.NewLoadChain(mockBF[2],
   581  								secboot.NewLoadChain(mockBF[4])),
   582  							secboot.NewLoadChain(mockBF[3],
   583  								secboot.NewLoadChain(mockBF[4]))),
   584  						secboot.NewLoadChain(mockBF[1],
   585  							secboot.NewLoadChain(mockBF[2],
   586  								secboot.NewLoadChain(mockBF[4])),
   587  							secboot.NewLoadChain(mockBF[3],
   588  								secboot.NewLoadChain(mockBF[4]))),
   589  					},
   590  					KernelCmdlines: []string{"cmdline2", "cmdline3"},
   591  					Model:          &asserts.Model{},
   592  				},
   593  			},
   594  			KeyFile:                 "keyfile",
   595  			TPMPolicyUpdateDataFile: "policy-update-data-file",
   596  			TPMLockoutAuthFile:      filepath.Join(tmpDir, "lockout-auth-file"),
   597  		}
   598  
   599  		myKey := secboot.EncryptionKey{}
   600  		for i := range myKey {
   601  			myKey[i] = byte(i)
   602  		}
   603  
   604  		// events for
   605  		// a -> kernel
   606  		sequences1 := []*sb.EFIImageLoadEvent{
   607  			{
   608  				Source: sb.Firmware,
   609  				Image:  sb.FileEFIImage(mockBF[0].Path),
   610  				Next: []*sb.EFIImageLoadEvent{
   611  					{
   612  						Source: sb.Shim,
   613  						Image: sb.SnapFileEFIImage{
   614  							Container: kernelSnap,
   615  							Path:      mockBF[4].Snap,
   616  							FileName:  "kernel.efi",
   617  						},
   618  					},
   619  				},
   620  			},
   621  		}
   622  
   623  		// "cdk" events for
   624  		// c -> kernel OR
   625  		// d -> kernel
   626  		cdk := []*sb.EFIImageLoadEvent{
   627  			{
   628  				Source: sb.Shim,
   629  				Image:  sb.FileEFIImage(mockBF[2].Path),
   630  				Next: []*sb.EFIImageLoadEvent{
   631  					{
   632  						Source: sb.Shim,
   633  						Image: sb.SnapFileEFIImage{
   634  							Container: kernelSnap,
   635  							Path:      mockBF[4].Snap,
   636  							FileName:  "kernel.efi",
   637  						},
   638  					},
   639  				},
   640  			},
   641  			{
   642  				Source: sb.Shim,
   643  				Image:  sb.FileEFIImage(mockBF[3].Path),
   644  				Next: []*sb.EFIImageLoadEvent{
   645  					{
   646  						Source: sb.Shim,
   647  						Image: sb.SnapFileEFIImage{
   648  							Container: kernelSnap,
   649  							Path:      mockBF[4].Snap,
   650  							FileName:  "kernel.efi",
   651  						},
   652  					},
   653  				},
   654  			},
   655  		}
   656  
   657  		// events for
   658  		// a -> "cdk"
   659  		// b -> "cdk"
   660  		sequences2 := []*sb.EFIImageLoadEvent{
   661  			{
   662  				Source: sb.Firmware,
   663  				Image:  sb.FileEFIImage(mockBF[0].Path),
   664  				Next:   cdk,
   665  			},
   666  			{
   667  				Source: sb.Firmware,
   668  				Image:  sb.FileEFIImage(mockBF[1].Path),
   669  				Next:   cdk,
   670  			},
   671  		}
   672  
   673  		tpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   674  		defer restore()
   675  
   676  		// mock adding EFI secure boot policy profile
   677  		var pcrProfile *sb.PCRProtectionProfile
   678  		addEFISbPolicyCalls := 0
   679  		restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error {
   680  			addEFISbPolicyCalls++
   681  			pcrProfile = profile
   682  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   683  			switch addEFISbPolicyCalls {
   684  			case 1:
   685  				c.Assert(params.LoadSequences, DeepEquals, sequences1)
   686  			case 2:
   687  				c.Assert(params.LoadSequences, DeepEquals, sequences2)
   688  			default:
   689  				c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time")
   690  			}
   691  			return tc.addEFISbPolicyErr
   692  		})
   693  		defer restore()
   694  
   695  		// mock adding systemd EFI stub profile
   696  		addSystemdEfiStubCalls := 0
   697  		restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error {
   698  			addSystemdEfiStubCalls++
   699  			c.Assert(profile, Equals, pcrProfile)
   700  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   701  			c.Assert(params.PCRIndex, Equals, 12)
   702  			switch addSystemdEfiStubCalls {
   703  			case 1:
   704  				c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines)
   705  			case 2:
   706  				c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines)
   707  			default:
   708  				c.Error("AddSystemdEFIStubProfile shouldn't be called a third time")
   709  			}
   710  			return tc.addSystemdEFIStubErr
   711  		})
   712  		defer restore()
   713  
   714  		// mock adding snap model profile
   715  		addSnapModelCalls := 0
   716  		restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error {
   717  			addSnapModelCalls++
   718  			c.Assert(profile, Equals, pcrProfile)
   719  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   720  			c.Assert(params.PCRIndex, Equals, 12)
   721  			switch addSnapModelCalls {
   722  			case 1:
   723  				c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model)
   724  			case 2:
   725  				c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[1].Model)
   726  			default:
   727  				c.Error("AddSnapModelProfile shouldn't be called a third time")
   728  			}
   729  			return tc.addSnapModelErr
   730  		})
   731  		defer restore()
   732  
   733  		// mock provisioning
   734  		provisioningCalls := 0
   735  		restore = secboot.MockSbProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error {
   736  			provisioningCalls++
   737  			c.Assert(t, Equals, tpm)
   738  			c.Assert(mode, Equals, sb.ProvisionModeFull)
   739  			c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent)
   740  			return tc.provisioningErr
   741  		})
   742  		defer restore()
   743  
   744  		// mock sealing
   745  		sealCalls := 0
   746  		restore = secboot.MockSbSealKeyToTPM(func(t *sb.TPMConnection, key []byte, keyPath, policyUpdatePath string, params *sb.KeyCreationParams) error {
   747  			sealCalls++
   748  			c.Assert(t, Equals, tpm)
   749  			c.Assert(key, DeepEquals, myKey[:])
   750  			c.Assert(keyPath, Equals, myParams.KeyFile)
   751  			c.Assert(policyUpdatePath, Equals, myParams.TPMPolicyUpdateDataFile)
   752  			c.Assert(params.PINHandle, Equals, tpm2.Handle(0x01880000))
   753  			return tc.sealErr
   754  		})
   755  		defer restore()
   756  
   757  		// mock TPM enabled check
   758  		restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool {
   759  			return tc.tpmEnabled
   760  		})
   761  		defer restore()
   762  
   763  		err := secboot.SealKey(myKey, &myParams)
   764  		if tc.expectedErr == "" {
   765  			c.Assert(err, IsNil)
   766  			c.Assert(addEFISbPolicyCalls, Equals, 2)
   767  			c.Assert(addSystemdEfiStubCalls, Equals, 2)
   768  			c.Assert(addSnapModelCalls, Equals, 2)
   769  		} else {
   770  			c.Assert(err, ErrorMatches, tc.expectedErr)
   771  		}
   772  		c.Assert(provisioningCalls, Equals, tc.provisioningCalls)
   773  		c.Assert(sealCalls, Equals, tc.sealCalls)
   774  
   775  	}
   776  }
   777  
   778  func (s *secbootSuite) TestSealKeyNoModelParams(c *C) {
   779  	myKey := secboot.EncryptionKey{}
   780  	myParams := secboot.SealKeyParams{
   781  		KeyFile:                 "keyfile",
   782  		TPMPolicyUpdateDataFile: "policy-update-data-file",
   783  		TPMLockoutAuthFile:      "lockout-auth-file",
   784  	}
   785  
   786  	err := secboot.SealKey(myKey, &myParams)
   787  	c.Assert(err, ErrorMatches, "at least one set of model-specific parameters is required")
   788  }
   789  
   790  func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, error) {
   791  	snapYamlPath := filepath.Join(snapDir, "meta/snap.yaml")
   792  	if err := os.MkdirAll(filepath.Dir(snapYamlPath), 0755); err != nil {
   793  		return nil, err
   794  	}
   795  	if err := ioutil.WriteFile(snapYamlPath, []byte("name: foo"), 0644); err != nil {
   796  		return nil, err
   797  	}
   798  	sqfs := squashfs.New(snapPath)
   799  	if err := sqfs.Build(snapDir, &squashfs.BuildOpts{SnapType: snapType}); err != nil {
   800  		return nil, err
   801  	}
   802  	return snapfile.Open(snapPath)
   803  }
   804  
   805  func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) {
   806  	tcti, err := os.Open("/dev/null")
   807  	c.Assert(err, IsNil)
   808  	tpmctx, err := tpm2.NewTPMContext(tcti)
   809  	c.Assert(err, IsNil)
   810  	tpm := &sb.TPMConnection{TPMContext: tpmctx}
   811  	restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) {
   812  		if tpmErr != nil {
   813  			return nil, tpmErr
   814  		}
   815  		return tpm, nil
   816  	})
   817  	return tpm, restore
   818  }