github.com/anonymouse64/snapd@v0.0.0-20210824153203-04c4c42d842d/secboot/secboot_sb_test.go (about)

     1  // -*- Mode: Go; indent-tabs-mode: t -*-
     2  // +build !nosecboot
     3  
     4  /*
     5   * Copyright (C) 2021 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  	"bytes"
    25  	"crypto/ecdsa"
    26  	"encoding/base64"
    27  	"encoding/json"
    28  	"errors"
    29  	"fmt"
    30  	"io"
    31  	"io/ioutil"
    32  	"os"
    33  	"path/filepath"
    34  
    35  	"github.com/canonical/go-tpm2"
    36  	sb "github.com/snapcore/secboot"
    37  	. "gopkg.in/check.v1"
    38  
    39  	"github.com/snapcore/snapd/asserts"
    40  	"github.com/snapcore/snapd/asserts/assertstest"
    41  	"github.com/snapcore/snapd/bootloader"
    42  	"github.com/snapcore/snapd/bootloader/efi"
    43  	"github.com/snapcore/snapd/dirs"
    44  	"github.com/snapcore/snapd/kernel/fde"
    45  	"github.com/snapcore/snapd/osutil"
    46  	"github.com/snapcore/snapd/osutil/disks"
    47  	"github.com/snapcore/snapd/secboot"
    48  	"github.com/snapcore/snapd/snap"
    49  	"github.com/snapcore/snapd/snap/snapfile"
    50  	"github.com/snapcore/snapd/snap/squashfs"
    51  	"github.com/snapcore/snapd/testutil"
    52  )
    53  
    54  type secbootSuite struct {
    55  	testutil.BaseTest
    56  }
    57  
    58  var _ = Suite(&secbootSuite{})
    59  
    60  func (s *secbootSuite) SetUpTest(c *C) {
    61  	rootDir := c.MkDir()
    62  	err := os.MkdirAll(filepath.Join(rootDir, "/run"), 0755)
    63  	c.Assert(err, IsNil)
    64  	dirs.SetRootDir(rootDir)
    65  	s.AddCleanup(func() { dirs.SetRootDir("/") })
    66  }
    67  
    68  func (s *secbootSuite) TestCheckTPMKeySealingSupported(c *C) {
    69  	c.Check(secboot.WithSecbootSupport, Equals, true)
    70  
    71  	sbEmpty := []uint8{}
    72  	sbEnabled := []uint8{1}
    73  	sbDisabled := []uint8{0}
    74  	efiNotSupported := []uint8(nil)
    75  	tpmErr := errors.New("TPM error")
    76  
    77  	type testCase struct {
    78  		tpmErr     error
    79  		tpmEnabled bool
    80  		sbData     []uint8
    81  		err        string
    82  	}
    83  	for i, tc := range []testCase{
    84  		// happy case
    85  		{tpmErr: nil, tpmEnabled: true, sbData: sbEnabled, err: ""},
    86  		// secure boot EFI var is empty
    87  		{tpmErr: nil, tpmEnabled: true, sbData: sbEmpty, err: "secure boot variable does not exist"},
    88  		// secure boot is disabled
    89  		{tpmErr: nil, tpmEnabled: true, sbData: sbDisabled, err: "secure boot is disabled"},
    90  		// EFI not supported
    91  		{tpmErr: nil, tpmEnabled: true, sbData: efiNotSupported, err: "not a supported EFI system"},
    92  		// TPM connection error
    93  		{tpmErr: tpmErr, sbData: sbEnabled, err: "cannot connect to TPM device: TPM error"},
    94  		// TPM was detected but it's not enabled
    95  		{tpmErr: nil, tpmEnabled: false, sbData: sbEnabled, err: "TPM device is not enabled"},
    96  		// No TPM device
    97  		{tpmErr: sb.ErrNoTPM2Device, sbData: sbEnabled, err: "cannot connect to TPM device: no TPM2 device is available"},
    98  	} {
    99  		c.Logf("%d: %v %v %v %q", i, tc.tpmErr, tc.tpmEnabled, tc.sbData, tc.err)
   100  
   101  		_, restore := mockSbTPMConnection(c, tc.tpmErr)
   102  		defer restore()
   103  
   104  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   105  			return tc.tpmEnabled
   106  		})
   107  		defer restore()
   108  
   109  		var vars map[string][]byte
   110  		if tc.sbData != nil {
   111  			vars = map[string][]byte{"SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c": tc.sbData}
   112  		}
   113  		restoreEfiVars := efi.MockVars(vars, nil)
   114  		defer restoreEfiVars()
   115  
   116  		err := secboot.CheckTPMKeySealingSupported()
   117  		if tc.err == "" {
   118  			c.Assert(err, IsNil)
   119  		} else {
   120  			c.Assert(err, ErrorMatches, tc.err)
   121  		}
   122  	}
   123  }
   124  
   125  func (s *secbootSuite) TestMeasureSnapSystemEpochWhenPossible(c *C) {
   126  	for _, tc := range []struct {
   127  		tpmErr     error
   128  		tpmEnabled bool
   129  		callNum    int
   130  		err        string
   131  	}{
   132  		{
   133  			// normal connection to the TPM device
   134  			tpmErr: nil, tpmEnabled: true, callNum: 1, err: "",
   135  		},
   136  		{
   137  			// TPM device exists but returns error
   138  			tpmErr: errors.New("tpm error"), callNum: 0,
   139  			err: "cannot measure snap system epoch: cannot open TPM connection: tpm error",
   140  		},
   141  		{
   142  			// TPM device exists but is disabled
   143  			tpmErr: nil, tpmEnabled: false,
   144  		},
   145  		{
   146  			// TPM device does not exist
   147  			tpmErr: sb.ErrNoTPM2Device,
   148  		},
   149  	} {
   150  		mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   151  		defer restore()
   152  
   153  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   154  			return tc.tpmEnabled
   155  		})
   156  		defer restore()
   157  
   158  		calls := 0
   159  		restore = secboot.MockSbMeasureSnapSystemEpochToTPM(func(tpm *sb.TPMConnection, pcrIndex int) error {
   160  			calls++
   161  			c.Assert(tpm, Equals, mockTpm)
   162  			c.Assert(pcrIndex, Equals, 12)
   163  			return nil
   164  		})
   165  		defer restore()
   166  
   167  		err := secboot.MeasureSnapSystemEpochWhenPossible()
   168  		if tc.err == "" {
   169  			c.Assert(err, IsNil)
   170  		} else {
   171  			c.Assert(err, ErrorMatches, tc.err)
   172  		}
   173  		c.Assert(calls, Equals, tc.callNum)
   174  	}
   175  }
   176  
   177  func (s *secbootSuite) TestMeasureSnapModelWhenPossible(c *C) {
   178  	for i, tc := range []struct {
   179  		tpmErr     error
   180  		tpmEnabled bool
   181  		modelErr   error
   182  		callNum    int
   183  		err        string
   184  	}{
   185  		{
   186  			// normal connection to the TPM device
   187  			tpmErr: nil, tpmEnabled: true, modelErr: nil, callNum: 1, err: "",
   188  		},
   189  		{
   190  			// normal connection to the TPM device with model error
   191  			tpmErr: nil, tpmEnabled: true, modelErr: errors.New("model error"), callNum: 0,
   192  			err: "cannot measure snap model: model error",
   193  		},
   194  		{
   195  			// TPM device exists but returns error
   196  			tpmErr: errors.New("tpm error"), callNum: 0,
   197  			err: "cannot measure snap model: cannot open TPM connection: tpm error",
   198  		},
   199  		{
   200  			// TPM device exists but is disabled
   201  			tpmErr: nil, tpmEnabled: false,
   202  		},
   203  		{
   204  			// TPM device does not exist
   205  			tpmErr: sb.ErrNoTPM2Device,
   206  		},
   207  	} {
   208  		c.Logf("%d: tpmErr:%v tpmEnabled:%v", i, tc.tpmErr, tc.tpmEnabled)
   209  		mockModel := &asserts.Model{}
   210  
   211  		mockTpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   212  		defer restore()
   213  
   214  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   215  			return tc.tpmEnabled
   216  		})
   217  		defer restore()
   218  
   219  		calls := 0
   220  		restore = secboot.MockSbMeasureSnapModelToTPM(func(tpm *sb.TPMConnection, pcrIndex int, model sb.SnapModel) error {
   221  			calls++
   222  			c.Assert(tpm, Equals, mockTpm)
   223  			c.Assert(model, Equals, mockModel)
   224  			c.Assert(pcrIndex, Equals, 12)
   225  			return nil
   226  		})
   227  		defer restore()
   228  
   229  		findModel := func() (*asserts.Model, error) {
   230  			if tc.modelErr != nil {
   231  				return nil, tc.modelErr
   232  			}
   233  			return mockModel, nil
   234  		}
   235  
   236  		err := secboot.MeasureSnapModelWhenPossible(findModel)
   237  		if tc.err == "" {
   238  			c.Assert(err, IsNil)
   239  		} else {
   240  			c.Assert(err, ErrorMatches, tc.err)
   241  		}
   242  		c.Assert(calls, Equals, tc.callNum)
   243  	}
   244  }
   245  
   246  func (s *secbootSuite) TestLockTPMSealedKeys(c *C) {
   247  	tt := []struct {
   248  		tpmErr     error
   249  		tpmEnabled bool
   250  		lockOk     bool
   251  		expError   string
   252  	}{
   253  		// can't connect to tpm
   254  		{
   255  			tpmErr:   fmt.Errorf("failed to connect to tpm"),
   256  			expError: "cannot lock TPM: failed to connect to tpm",
   257  		},
   258  		// no TPM2 device, shouldn't return an error
   259  		{
   260  			tpmErr: sb.ErrNoTPM2Device,
   261  		},
   262  		// tpm is not enabled but we can lock it
   263  		{
   264  			tpmEnabled: false,
   265  			lockOk:     true,
   266  		},
   267  		// can't lock pcr protection profile
   268  		{
   269  			tpmEnabled: true,
   270  			lockOk:     false,
   271  			expError:   "block failed",
   272  		},
   273  		// tpm enabled, we can lock it
   274  		{
   275  			tpmEnabled: true,
   276  			lockOk:     true,
   277  		},
   278  	}
   279  
   280  	for _, tc := range tt {
   281  		mockSbTPM, restoreConnect := mockSbTPMConnection(c, tc.tpmErr)
   282  		defer restoreConnect()
   283  
   284  		restore := secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   285  			return tc.tpmEnabled
   286  		})
   287  		defer restore()
   288  
   289  		sbBlockPCRProtectionPolicesCalls := 0
   290  		restore = secboot.MockSbBlockPCRProtectionPolicies(func(tpm *sb.TPMConnection, pcrs []int) error {
   291  			sbBlockPCRProtectionPolicesCalls++
   292  			c.Assert(tpm, Equals, mockSbTPM)
   293  			c.Assert(pcrs, DeepEquals, []int{12})
   294  			if tc.lockOk {
   295  				return nil
   296  			}
   297  			return errors.New("block failed")
   298  		})
   299  		defer restore()
   300  
   301  		err := secboot.LockTPMSealedKeys()
   302  		if tc.expError == "" {
   303  			c.Assert(err, IsNil)
   304  		} else {
   305  			c.Assert(err, ErrorMatches, tc.expError)
   306  		}
   307  		// if there was no TPM connection error, we should have tried to lock it
   308  		if tc.tpmErr == nil {
   309  			c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 1)
   310  		} else {
   311  			c.Assert(sbBlockPCRProtectionPolicesCalls, Equals, 0)
   312  		}
   313  	}
   314  }
   315  
   316  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncrypted(c *C) {
   317  
   318  	// setup mock disks to use for locating the partition
   319  	// restore := disks.MockMountPointDisksToPartitionMapping()
   320  	// defer restore()
   321  
   322  	mockDiskWithEncDev := &disks.MockDiskMapping{
   323  		FilesystemLabelToPartUUID: map[string]string{
   324  			"name-enc": "enc-dev-partuuid",
   325  		},
   326  	}
   327  
   328  	mockDiskWithoutAnyDev := &disks.MockDiskMapping{
   329  		FilesystemLabelToPartUUID: map[string]string{},
   330  	}
   331  
   332  	mockDiskWithUnencDev := &disks.MockDiskMapping{
   333  		FilesystemLabelToPartUUID: map[string]string{
   334  			"name": "unenc-dev-partuuid",
   335  		},
   336  	}
   337  
   338  	for idx, tc := range []struct {
   339  		tpmErr              error
   340  		keyfile             string // the keyfile to be used to unseal
   341  		tpmEnabled          bool   // TPM storage and endorsement hierarchies disabled, only relevant if TPM available
   342  		hasEncdev           bool   // an encrypted device exists
   343  		rkAllow             bool   // allow recovery key activation
   344  		rkErr               error  // recovery key unlock error, only relevant if TPM not available
   345  		activated           bool   // the activation operation succeeded
   346  		activateErr         error  // the activation error
   347  		err                 string
   348  		skipDiskEnsureCheck bool // whether to check to ensure the mock disk contains the device label
   349  		expUnlockMethod     secboot.UnlockMethod
   350  		disk                *disks.MockDiskMapping
   351  	}{
   352  		{
   353  			// happy case with tpm and encrypted device
   354  			tpmEnabled: true, hasEncdev: true,
   355  			activated:       true,
   356  			disk:            mockDiskWithEncDev,
   357  			expUnlockMethod: secboot.UnlockedWithSealedKey,
   358  		}, {
   359  			// happy case with tpm and encrypted device
   360  			// with an alternative keyfile
   361  			tpmEnabled: true, hasEncdev: true,
   362  			activated:       true,
   363  			disk:            mockDiskWithEncDev,
   364  			keyfile:         "some-other-keyfile",
   365  			expUnlockMethod: secboot.UnlockedWithSealedKey,
   366  		}, {
   367  			// device activation fails
   368  			tpmEnabled: true, hasEncdev: true,
   369  			err:  "cannot activate encrypted device .*: activation error",
   370  			disk: mockDiskWithEncDev,
   371  		}, {
   372  			// device activation fails
   373  			tpmEnabled: true, hasEncdev: true,
   374  			err:  "cannot activate encrypted device .*: activation error",
   375  			disk: mockDiskWithEncDev,
   376  		}, {
   377  			// happy case without encrypted device
   378  			tpmEnabled: true,
   379  			disk:       mockDiskWithUnencDev,
   380  		}, {
   381  			// happy case with tpm and encrypted device, activation
   382  			// with recovery key
   383  			tpmEnabled: true, hasEncdev: true, activated: true,
   384  			activateErr: &sb.ActivateWithTPMSealedKeyError{
   385  				// activation error with nil recovery key error
   386  				// implies volume activated successfully using
   387  				// the recovery key,
   388  				RecoveryKeyUsageErr: nil,
   389  			},
   390  			disk:            mockDiskWithEncDev,
   391  			expUnlockMethod: secboot.UnlockedWithRecoveryKey,
   392  		}, {
   393  			// tpm and encrypted device, successful activation, but
   394  			// recovery key non-nil is an unexpected state
   395  			tpmEnabled: true, hasEncdev: true, activated: true,
   396  			activateErr: &sb.ActivateWithTPMSealedKeyError{
   397  				RecoveryKeyUsageErr: fmt.Errorf("unexpected"),
   398  			},
   399  			expUnlockMethod: secboot.UnlockStatusUnknown,
   400  			err:             `internal error: volume activated with unexpected error: .* \(unexpected\)`,
   401  			disk:            mockDiskWithEncDev,
   402  		}, {
   403  			// tpm error, no encrypted device
   404  			tpmErr: errors.New("tpm error"),
   405  			disk:   mockDiskWithUnencDev,
   406  		}, {
   407  			// tpm error, has encrypted device
   408  			tpmErr: errors.New("tpm error"), hasEncdev: true,
   409  			err:  `cannot unlock encrypted device "name": tpm error`,
   410  			disk: mockDiskWithEncDev,
   411  		}, {
   412  			// tpm disabled, no encrypted device
   413  			disk: mockDiskWithUnencDev,
   414  		}, {
   415  			// tpm disabled, has encrypted device, unlocked using the recovery key
   416  			hasEncdev:       true,
   417  			rkAllow:         true,
   418  			disk:            mockDiskWithEncDev,
   419  			expUnlockMethod: secboot.UnlockedWithRecoveryKey,
   420  		}, {
   421  			// tpm disabled, has encrypted device, recovery key unlocking fails
   422  			hasEncdev: true, rkErr: errors.New("cannot unlock with recovery key"),
   423  			rkAllow: true,
   424  			disk:    mockDiskWithEncDev,
   425  			err:     `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
   426  		}, {
   427  			// no tpm, has encrypted device, unlocked using the recovery key
   428  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
   429  			rkAllow:         true,
   430  			disk:            mockDiskWithEncDev,
   431  			expUnlockMethod: secboot.UnlockedWithRecoveryKey,
   432  		}, {
   433  			// no tpm, has encrypted device, unlocking with recovery key not allowed
   434  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
   435  			disk: mockDiskWithEncDev,
   436  			err:  `cannot activate encrypted device ".*/enc-dev-partuuid": activation error`,
   437  		}, {
   438  			// no tpm, has encrypted device, recovery key unlocking fails
   439  			rkErr:  errors.New("cannot unlock with recovery key"),
   440  			tpmErr: sb.ErrNoTPM2Device, hasEncdev: true,
   441  			rkAllow: true,
   442  			disk:    mockDiskWithEncDev,
   443  			err:     `cannot unlock encrypted device ".*/enc-dev-partuuid": cannot unlock with recovery key`,
   444  		}, {
   445  			// no tpm, no encrypted device
   446  			tpmErr: sb.ErrNoTPM2Device,
   447  			disk:   mockDiskWithUnencDev,
   448  		}, {
   449  			// no disks at all
   450  			disk:                mockDiskWithoutAnyDev,
   451  			skipDiskEnsureCheck: true,
   452  			// error is specifically for failing to find name, NOT name-enc, we
   453  			// will properly fall back to looking for name if we didn't find
   454  			// name-enc
   455  			err: "error enumerating partitions for disk to find unencrypted device \"name\": filesystem label \"name\" not found",
   456  		},
   457  	} {
   458  		randomUUID := fmt.Sprintf("random-uuid-for-test-%d", idx)
   459  		restore := secboot.MockRandomKernelUUID(func() string {
   460  			return randomUUID
   461  		})
   462  		defer restore()
   463  
   464  		c.Logf("tc %v: %+v", idx, tc)
   465  		_, restoreConnect := mockSbTPMConnection(c, tc.tpmErr)
   466  		defer restoreConnect()
   467  
   468  		restore = secboot.MockIsTPMEnabled(func(tpm *sb.TPMConnection) bool {
   469  			return tc.tpmEnabled
   470  		})
   471  		defer restore()
   472  
   473  		defaultDevice := "name"
   474  
   475  		fsLabel := defaultDevice
   476  		if tc.hasEncdev {
   477  			fsLabel += "-enc"
   478  		}
   479  		partuuid, ok := tc.disk.FilesystemLabelToPartUUID[fsLabel]
   480  		if !tc.skipDiskEnsureCheck {
   481  			c.Assert(ok, Equals, true)
   482  		}
   483  		devicePath := filepath.Join("/dev/disk/by-partuuid", partuuid)
   484  
   485  		expKeyPath := tc.keyfile
   486  		if expKeyPath == "" {
   487  			expKeyPath = "vanilla-keyfile"
   488  		}
   489  
   490  		restore = secboot.MockSbActivateVolumeWithTPMSealedKey(func(tpm *sb.TPMConnection, volumeName, sourceDevicePath,
   491  			keyPath string, pinReader io.Reader, options *sb.ActivateVolumeOptions) (bool, error) {
   492  			c.Assert(volumeName, Equals, "name-"+randomUUID)
   493  			c.Assert(sourceDevicePath, Equals, devicePath)
   494  			c.Assert(keyPath, Equals, expKeyPath)
   495  			if tc.rkAllow {
   496  				c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{
   497  					PassphraseTries:  1,
   498  					RecoveryKeyTries: 3,
   499  					KeyringPrefix:    "ubuntu-fde",
   500  				})
   501  			} else {
   502  				c.Assert(*options, DeepEquals, sb.ActivateVolumeOptions{
   503  					PassphraseTries: 1,
   504  					// activation with recovery key was disabled
   505  					RecoveryKeyTries: 0,
   506  					KeyringPrefix:    "ubuntu-fde",
   507  				})
   508  			}
   509  			if !tc.activated && tc.activateErr == nil {
   510  				return false, errors.New("activation error")
   511  			}
   512  			return tc.activated, tc.activateErr
   513  		})
   514  		defer restore()
   515  
   516  		restore = secboot.MockSbActivateVolumeWithRecoveryKey(func(name, device string, keyReader io.Reader,
   517  			options *sb.ActivateVolumeOptions) error {
   518  			if !tc.rkAllow {
   519  				c.Fatalf("unexpected attempt to activate with recovery key")
   520  				return fmt.Errorf("unexpected call")
   521  			}
   522  			return tc.rkErr
   523  		})
   524  		defer restore()
   525  
   526  		opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
   527  			AllowRecoveryKey: tc.rkAllow,
   528  		}
   529  		unlockRes, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(tc.disk, defaultDevice, expKeyPath, opts)
   530  		if tc.err == "" {
   531  			c.Assert(err, IsNil)
   532  			c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev)
   533  			c.Assert(unlockRes.PartDevice, Equals, devicePath)
   534  			if tc.hasEncdev {
   535  				c.Assert(unlockRes.FsDevice, Equals, filepath.Join("/dev/mapper", defaultDevice+"-"+randomUUID))
   536  			} else {
   537  				c.Assert(unlockRes.FsDevice, Equals, devicePath)
   538  			}
   539  		} else {
   540  			c.Assert(err, ErrorMatches, tc.err)
   541  			// also check that the IsEncrypted value matches, this is
   542  			// important for robust callers to know whether they should try to
   543  			// unlock using a different method or not
   544  			// this is only skipped on some test cases where we get an error
   545  			// very early, like trying to connect to the tpm
   546  			c.Assert(unlockRes.IsEncrypted, Equals, tc.hasEncdev)
   547  			if tc.hasEncdev {
   548  				c.Check(unlockRes.PartDevice, Equals, devicePath)
   549  				c.Check(unlockRes.FsDevice, Equals, "")
   550  			} else {
   551  				c.Check(unlockRes.PartDevice, Equals, "")
   552  				c.Check(unlockRes.FsDevice, Equals, "")
   553  			}
   554  		}
   555  
   556  		c.Assert(unlockRes.UnlockMethod, Equals, tc.expUnlockMethod)
   557  	}
   558  }
   559  
   560  func (s *secbootSuite) TestEFIImageFromBootFile(c *C) {
   561  	tmpDir := c.MkDir()
   562  
   563  	// set up some test files
   564  	existingFile := filepath.Join(tmpDir, "foo")
   565  	err := ioutil.WriteFile(existingFile, nil, 0644)
   566  	c.Assert(err, IsNil)
   567  	missingFile := filepath.Join(tmpDir, "bar")
   568  	snapFile := filepath.Join(tmpDir, "test.snap")
   569  	snapf, err := createMockSnapFile(c.MkDir(), snapFile, "app")
   570  
   571  	for _, tc := range []struct {
   572  		bootFile bootloader.BootFile
   573  		efiImage sb.EFIImage
   574  		err      string
   575  	}{
   576  		{
   577  			// happy case for EFI image
   578  			bootFile: bootloader.NewBootFile("", existingFile, bootloader.RoleRecovery),
   579  			efiImage: sb.FileEFIImage(existingFile),
   580  		},
   581  		{
   582  			// missing EFI image
   583  			bootFile: bootloader.NewBootFile("", missingFile, bootloader.RoleRecovery),
   584  			err:      fmt.Sprintf("file %s/bar does not exist", tmpDir),
   585  		},
   586  		{
   587  			// happy case for snap file
   588  			bootFile: bootloader.NewBootFile(snapFile, "rel", bootloader.RoleRecovery),
   589  			efiImage: sb.SnapFileEFIImage{Container: snapf, FileName: "rel"},
   590  		},
   591  		{
   592  			// invalid snap file
   593  			bootFile: bootloader.NewBootFile(existingFile, "rel", bootloader.RoleRecovery),
   594  			err:      fmt.Sprintf(`"%s/foo" is not a snap or snapdir`, tmpDir),
   595  		},
   596  		{
   597  			// missing snap file
   598  			bootFile: bootloader.NewBootFile(missingFile, "rel", bootloader.RoleRecovery),
   599  			err:      fmt.Sprintf(`"%s/bar" is not a snap or snapdir`, tmpDir),
   600  		},
   601  	} {
   602  		o, err := secboot.EFIImageFromBootFile(&tc.bootFile)
   603  		if tc.err == "" {
   604  			c.Assert(err, IsNil)
   605  			c.Assert(o, DeepEquals, tc.efiImage)
   606  		} else {
   607  			c.Assert(err, ErrorMatches, tc.err)
   608  		}
   609  	}
   610  }
   611  
   612  func (s *secbootSuite) TestSealKey(c *C) {
   613  	mockErr := errors.New("some error")
   614  
   615  	for _, tc := range []struct {
   616  		tpmErr               error
   617  		tpmEnabled           bool
   618  		missingFile          bool
   619  		badSnapFile          bool
   620  		skipProvision        bool
   621  		addEFISbPolicyErr    error
   622  		addEFIBootManagerErr error
   623  		addSystemdEFIStubErr error
   624  		addSnapModelErr      error
   625  		provisioningErr      error
   626  		sealErr              error
   627  		provisioningCalls    int
   628  		sealCalls            int
   629  		expectedErr          string
   630  	}{
   631  		{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
   632  		{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
   633  		{tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file /does/not/exist does not exist"},
   634  		{tpmEnabled: true, badSnapFile: true, expectedErr: `.*/kernel.snap" is not a snap or snapdir`},
   635  		{tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"},
   636  		{tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"},
   637  		{tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
   638  		{tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
   639  		{tpmEnabled: true, provisioningErr: mockErr, provisioningCalls: 1, expectedErr: "cannot provision TPM: some error"},
   640  		{tpmEnabled: true, sealErr: mockErr, provisioningCalls: 1, sealCalls: 1, expectedErr: "some error"},
   641  		{tpmEnabled: true, skipProvision: true, provisioningCalls: 0, sealCalls: 1, expectedErr: ""},
   642  		{tpmEnabled: true, provisioningCalls: 1, sealCalls: 1, expectedErr: ""},
   643  	} {
   644  		tmpDir := c.MkDir()
   645  		var mockBF []bootloader.BootFile
   646  		for _, name := range []string{"a", "b", "c", "d"} {
   647  			mockFileName := filepath.Join(tmpDir, name)
   648  			err := ioutil.WriteFile(mockFileName, nil, 0644)
   649  			c.Assert(err, IsNil)
   650  			mockBF = append(mockBF, bootloader.NewBootFile("", mockFileName, bootloader.RoleRecovery))
   651  		}
   652  
   653  		if tc.missingFile {
   654  			mockBF[0].Path = "/does/not/exist"
   655  		}
   656  
   657  		var kernelSnap snap.Container
   658  		snapPath := filepath.Join(tmpDir, "kernel.snap")
   659  		if tc.badSnapFile {
   660  			err := ioutil.WriteFile(snapPath, nil, 0644)
   661  			c.Assert(err, IsNil)
   662  		} else {
   663  			var err error
   664  			kernelSnap, err = createMockSnapFile(c.MkDir(), snapPath, "kernel")
   665  			c.Assert(err, IsNil)
   666  		}
   667  
   668  		mockBF = append(mockBF, bootloader.NewBootFile(snapPath, "kernel.efi", bootloader.RoleRecovery))
   669  
   670  		myAuthKey := &ecdsa.PrivateKey{}
   671  
   672  		myParams := secboot.SealKeysParams{
   673  			ModelParams: []*secboot.SealKeyModelParams{
   674  				{
   675  					EFILoadChains: []*secboot.LoadChain{
   676  						secboot.NewLoadChain(mockBF[0],
   677  							secboot.NewLoadChain(mockBF[4])),
   678  					},
   679  					KernelCmdlines: []string{"cmdline1"},
   680  					Model:          &asserts.Model{},
   681  				},
   682  				{
   683  					EFILoadChains: []*secboot.LoadChain{
   684  						secboot.NewLoadChain(mockBF[0],
   685  							secboot.NewLoadChain(mockBF[2],
   686  								secboot.NewLoadChain(mockBF[4])),
   687  							secboot.NewLoadChain(mockBF[3],
   688  								secboot.NewLoadChain(mockBF[4]))),
   689  						secboot.NewLoadChain(mockBF[1],
   690  							secboot.NewLoadChain(mockBF[2],
   691  								secboot.NewLoadChain(mockBF[4])),
   692  							secboot.NewLoadChain(mockBF[3],
   693  								secboot.NewLoadChain(mockBF[4]))),
   694  					},
   695  					KernelCmdlines: []string{"cmdline2", "cmdline3"},
   696  					Model:          &asserts.Model{},
   697  				},
   698  			},
   699  			TPMPolicyAuthKey:       myAuthKey,
   700  			TPMPolicyAuthKeyFile:   filepath.Join(tmpDir, "policy-auth-key-file"),
   701  			TPMLockoutAuthFile:     filepath.Join(tmpDir, "lockout-auth-file"),
   702  			TPMProvision:           !tc.skipProvision,
   703  			PCRPolicyCounterHandle: 42,
   704  		}
   705  
   706  		myKey := secboot.EncryptionKey{}
   707  		myKey2 := secboot.EncryptionKey{}
   708  		for i := range myKey {
   709  			myKey[i] = byte(i)
   710  			myKey2[i] = byte(128 + i)
   711  		}
   712  
   713  		myKeys := []secboot.SealKeyRequest{
   714  			{
   715  				Key:     myKey,
   716  				KeyFile: "keyfile",
   717  			},
   718  			{
   719  				Key:     myKey2,
   720  				KeyFile: "keyfile2",
   721  			},
   722  		}
   723  
   724  		// events for
   725  		// a -> kernel
   726  		sequences1 := []*sb.EFIImageLoadEvent{
   727  			{
   728  				Source: sb.Firmware,
   729  				Image:  sb.FileEFIImage(mockBF[0].Path),
   730  				Next: []*sb.EFIImageLoadEvent{
   731  					{
   732  						Source: sb.Shim,
   733  						Image: sb.SnapFileEFIImage{
   734  							Container: kernelSnap,
   735  							FileName:  "kernel.efi",
   736  						},
   737  					},
   738  				},
   739  			},
   740  		}
   741  
   742  		// "cdk" events for
   743  		// c -> kernel OR
   744  		// d -> kernel
   745  		cdk := []*sb.EFIImageLoadEvent{
   746  			{
   747  				Source: sb.Shim,
   748  				Image:  sb.FileEFIImage(mockBF[2].Path),
   749  				Next: []*sb.EFIImageLoadEvent{
   750  					{
   751  						Source: sb.Shim,
   752  						Image: sb.SnapFileEFIImage{
   753  							Container: kernelSnap,
   754  							FileName:  "kernel.efi",
   755  						},
   756  					},
   757  				},
   758  			},
   759  			{
   760  				Source: sb.Shim,
   761  				Image:  sb.FileEFIImage(mockBF[3].Path),
   762  				Next: []*sb.EFIImageLoadEvent{
   763  					{
   764  						Source: sb.Shim,
   765  						Image: sb.SnapFileEFIImage{
   766  							Container: kernelSnap,
   767  							FileName:  "kernel.efi",
   768  						},
   769  					},
   770  				},
   771  			},
   772  		}
   773  
   774  		// events for
   775  		// a -> "cdk"
   776  		// b -> "cdk"
   777  		sequences2 := []*sb.EFIImageLoadEvent{
   778  			{
   779  				Source: sb.Firmware,
   780  				Image:  sb.FileEFIImage(mockBF[0].Path),
   781  				Next:   cdk,
   782  			},
   783  			{
   784  				Source: sb.Firmware,
   785  				Image:  sb.FileEFIImage(mockBF[1].Path),
   786  				Next:   cdk,
   787  			},
   788  		}
   789  
   790  		tpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   791  		defer restore()
   792  
   793  		// mock adding EFI secure boot policy profile
   794  		var pcrProfile *sb.PCRProtectionProfile
   795  		addEFISbPolicyCalls := 0
   796  		restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error {
   797  			addEFISbPolicyCalls++
   798  			pcrProfile = profile
   799  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   800  			switch addEFISbPolicyCalls {
   801  			case 1:
   802  				c.Assert(params.LoadSequences, DeepEquals, sequences1)
   803  			case 2:
   804  				c.Assert(params.LoadSequences, DeepEquals, sequences2)
   805  			default:
   806  				c.Error("AddEFISecureBootPolicyProfile shouldn't be called a third time")
   807  			}
   808  			return tc.addEFISbPolicyErr
   809  		})
   810  		defer restore()
   811  
   812  		// mock adding EFI boot manager profile
   813  		addEFIBootManagerCalls := 0
   814  		restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error {
   815  			addEFIBootManagerCalls++
   816  			c.Assert(profile, Equals, pcrProfile)
   817  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   818  			switch addEFISbPolicyCalls {
   819  			case 1:
   820  				c.Assert(params.LoadSequences, DeepEquals, sequences1)
   821  			case 2:
   822  				c.Assert(params.LoadSequences, DeepEquals, sequences2)
   823  			default:
   824  				c.Error("AddEFIBootManagerProfile shouldn't be called a third time")
   825  			}
   826  			return tc.addEFIBootManagerErr
   827  		})
   828  		defer restore()
   829  
   830  		// mock adding systemd EFI stub profile
   831  		addSystemdEfiStubCalls := 0
   832  		restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error {
   833  			addSystemdEfiStubCalls++
   834  			c.Assert(profile, Equals, pcrProfile)
   835  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   836  			c.Assert(params.PCRIndex, Equals, 12)
   837  			switch addSystemdEfiStubCalls {
   838  			case 1:
   839  				c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines)
   840  			case 2:
   841  				c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[1].KernelCmdlines)
   842  			default:
   843  				c.Error("AddSystemdEFIStubProfile shouldn't be called a third time")
   844  			}
   845  			return tc.addSystemdEFIStubErr
   846  		})
   847  		defer restore()
   848  
   849  		// mock adding snap model profile
   850  		addSnapModelCalls := 0
   851  		restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error {
   852  			addSnapModelCalls++
   853  			c.Assert(profile, Equals, pcrProfile)
   854  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   855  			c.Assert(params.PCRIndex, Equals, 12)
   856  			switch addSnapModelCalls {
   857  			case 1:
   858  				c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model)
   859  			case 2:
   860  				c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[1].Model)
   861  			default:
   862  				c.Error("AddSnapModelProfile shouldn't be called a third time")
   863  			}
   864  			return tc.addSnapModelErr
   865  		})
   866  		defer restore()
   867  
   868  		// mock provisioning
   869  		provisioningCalls := 0
   870  		restore = secboot.MockProvisionTPM(func(t *sb.TPMConnection, mode sb.ProvisionMode, newLockoutAuth []byte) error {
   871  			provisioningCalls++
   872  			c.Assert(t, Equals, tpm)
   873  			c.Assert(mode, Equals, sb.ProvisionModeFull)
   874  			c.Assert(myParams.TPMLockoutAuthFile, testutil.FilePresent)
   875  			return tc.provisioningErr
   876  		})
   877  		defer restore()
   878  
   879  		// mock sealing
   880  		sealCalls := 0
   881  		restore = secboot.MockSbSealKeyToTPMMultiple(func(t *sb.TPMConnection, kr []*sb.SealKeyRequest, params *sb.KeyCreationParams) (sb.TPMPolicyAuthKey, error) {
   882  			sealCalls++
   883  			c.Assert(t, Equals, tpm)
   884  			c.Assert(kr, DeepEquals, []*sb.SealKeyRequest{{Key: myKey, Path: "keyfile"}, {Key: myKey2, Path: "keyfile2"}})
   885  			c.Assert(params.AuthKey, Equals, myAuthKey)
   886  			c.Assert(params.PCRPolicyCounterHandle, Equals, tpm2.Handle(42))
   887  			return sb.TPMPolicyAuthKey{}, tc.sealErr
   888  		})
   889  		defer restore()
   890  
   891  		// mock TPM enabled check
   892  		restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool {
   893  			return tc.tpmEnabled
   894  		})
   895  		defer restore()
   896  
   897  		err := secboot.SealKeys(myKeys, &myParams)
   898  		if tc.expectedErr == "" {
   899  			c.Assert(err, IsNil)
   900  			c.Assert(addEFISbPolicyCalls, Equals, 2)
   901  			c.Assert(addSystemdEfiStubCalls, Equals, 2)
   902  			c.Assert(addSnapModelCalls, Equals, 2)
   903  			c.Assert(osutil.FileExists(myParams.TPMPolicyAuthKeyFile), Equals, true)
   904  		} else {
   905  			c.Assert(err, ErrorMatches, tc.expectedErr)
   906  		}
   907  		c.Assert(provisioningCalls, Equals, tc.provisioningCalls)
   908  		c.Assert(sealCalls, Equals, tc.sealCalls)
   909  
   910  	}
   911  }
   912  
   913  func (s *secbootSuite) TestResealKey(c *C) {
   914  	mockErr := errors.New("some error")
   915  
   916  	for _, tc := range []struct {
   917  		tpmErr               error
   918  		tpmEnabled           bool
   919  		missingFile          bool
   920  		addEFISbPolicyErr    error
   921  		addEFIBootManagerErr error
   922  		addSystemdEFIStubErr error
   923  		addSnapModelErr      error
   924  		provisioningErr      error
   925  		resealErr            error
   926  		resealCalls          int
   927  		expectedErr          string
   928  	}{
   929  		{tpmErr: mockErr, expectedErr: "cannot connect to TPM: some error"},
   930  		{tpmEnabled: false, expectedErr: "TPM device is not enabled"},
   931  		{tpmEnabled: true, missingFile: true, expectedErr: "cannot build EFI image load sequences: file .*/file.efi does not exist"},
   932  		{tpmEnabled: true, addEFISbPolicyErr: mockErr, expectedErr: "cannot add EFI secure boot policy profile: some error"},
   933  		{tpmEnabled: true, addEFIBootManagerErr: mockErr, expectedErr: "cannot add EFI boot manager profile: some error"},
   934  		{tpmEnabled: true, addSystemdEFIStubErr: mockErr, expectedErr: "cannot add systemd EFI stub profile: some error"},
   935  		{tpmEnabled: true, addSnapModelErr: mockErr, expectedErr: "cannot add snap model profile: some error"},
   936  		{tpmEnabled: true, resealErr: mockErr, resealCalls: 1, expectedErr: "some error"},
   937  		{tpmEnabled: true, resealCalls: 1, expectedErr: ""},
   938  	} {
   939  		mockTPMPolicyAuthKey := []byte{1, 3, 3, 7}
   940  		mockTPMPolicyAuthKeyFile := filepath.Join(c.MkDir(), "policy-auth-key-file")
   941  		err := ioutil.WriteFile(mockTPMPolicyAuthKeyFile, mockTPMPolicyAuthKey, 0600)
   942  		c.Assert(err, IsNil)
   943  
   944  		mockEFI := bootloader.NewBootFile("", filepath.Join(c.MkDir(), "file.efi"), bootloader.RoleRecovery)
   945  		if !tc.missingFile {
   946  			err := ioutil.WriteFile(mockEFI.Path, nil, 0644)
   947  			c.Assert(err, IsNil)
   948  		}
   949  
   950  		myParams := &secboot.ResealKeysParams{
   951  			ModelParams: []*secboot.SealKeyModelParams{
   952  				{
   953  					EFILoadChains:  []*secboot.LoadChain{secboot.NewLoadChain(mockEFI)},
   954  					KernelCmdlines: []string{"cmdline"},
   955  					Model:          &asserts.Model{},
   956  				},
   957  			},
   958  			KeyFiles:             []string{"keyfile", "keyfile2"},
   959  			TPMPolicyAuthKeyFile: mockTPMPolicyAuthKeyFile,
   960  		}
   961  
   962  		sequences := []*sb.EFIImageLoadEvent{
   963  			{
   964  				Source: sb.Firmware,
   965  				Image:  sb.FileEFIImage(mockEFI.Path),
   966  			},
   967  		}
   968  
   969  		// mock TPM connection
   970  		tpm, restore := mockSbTPMConnection(c, tc.tpmErr)
   971  		defer restore()
   972  
   973  		// mock TPM enabled check
   974  		restore = secboot.MockIsTPMEnabled(func(t *sb.TPMConnection) bool {
   975  			return tc.tpmEnabled
   976  		})
   977  		defer restore()
   978  
   979  		// mock adding EFI secure boot policy profile
   980  		var pcrProfile *sb.PCRProtectionProfile
   981  		addEFISbPolicyCalls := 0
   982  		restore = secboot.MockSbAddEFISecureBootPolicyProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFISecureBootPolicyProfileParams) error {
   983  			addEFISbPolicyCalls++
   984  			pcrProfile = profile
   985  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   986  			c.Assert(params.LoadSequences, DeepEquals, sequences)
   987  			return tc.addEFISbPolicyErr
   988  		})
   989  		defer restore()
   990  
   991  		// mock adding EFI boot manager profile
   992  		addEFIBootManagerCalls := 0
   993  		restore = secboot.MockSbAddEFIBootManagerProfile(func(profile *sb.PCRProtectionProfile, params *sb.EFIBootManagerProfileParams) error {
   994  			addEFIBootManagerCalls++
   995  			c.Assert(profile, Equals, pcrProfile)
   996  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
   997  			c.Assert(params.LoadSequences, DeepEquals, sequences)
   998  			return tc.addEFIBootManagerErr
   999  		})
  1000  		defer restore()
  1001  
  1002  		// mock adding systemd EFI stub profile
  1003  		addSystemdEfiStubCalls := 0
  1004  		restore = secboot.MockSbAddSystemdEFIStubProfile(func(profile *sb.PCRProtectionProfile, params *sb.SystemdEFIStubProfileParams) error {
  1005  			addSystemdEfiStubCalls++
  1006  			c.Assert(profile, Equals, pcrProfile)
  1007  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
  1008  			c.Assert(params.PCRIndex, Equals, 12)
  1009  			c.Assert(params.KernelCmdlines, DeepEquals, myParams.ModelParams[0].KernelCmdlines)
  1010  			return tc.addSystemdEFIStubErr
  1011  		})
  1012  		defer restore()
  1013  
  1014  		// mock adding snap model profile
  1015  		addSnapModelCalls := 0
  1016  		restore = secboot.MockSbAddSnapModelProfile(func(profile *sb.PCRProtectionProfile, params *sb.SnapModelProfileParams) error {
  1017  			addSnapModelCalls++
  1018  			c.Assert(profile, Equals, pcrProfile)
  1019  			c.Assert(params.PCRAlgorithm, Equals, tpm2.HashAlgorithmSHA256)
  1020  			c.Assert(params.PCRIndex, Equals, 12)
  1021  			c.Assert(params.Models[0], DeepEquals, myParams.ModelParams[0].Model)
  1022  			return tc.addSnapModelErr
  1023  		})
  1024  		defer restore()
  1025  
  1026  		// mock PCR protection policy update
  1027  		resealCalls := 0
  1028  		restore = secboot.MockSbUpdateKeyPCRProtectionPolicyMultiple(func(t *sb.TPMConnection, keyPaths []string, authKey sb.TPMPolicyAuthKey, profile *sb.PCRProtectionProfile) error {
  1029  			resealCalls++
  1030  			c.Assert(t, Equals, tpm)
  1031  			c.Assert(keyPaths, DeepEquals, []string{"keyfile", "keyfile2"})
  1032  			c.Assert(authKey, DeepEquals, sb.TPMPolicyAuthKey(mockTPMPolicyAuthKey))
  1033  			c.Assert(profile, Equals, pcrProfile)
  1034  			return tc.resealErr
  1035  		})
  1036  		defer restore()
  1037  
  1038  		err = secboot.ResealKeys(myParams)
  1039  		if tc.expectedErr == "" {
  1040  			c.Assert(err, IsNil)
  1041  			c.Assert(addEFISbPolicyCalls, Equals, 1)
  1042  			c.Assert(addSystemdEfiStubCalls, Equals, 1)
  1043  			c.Assert(addSnapModelCalls, Equals, 1)
  1044  		} else {
  1045  			c.Assert(err, ErrorMatches, tc.expectedErr)
  1046  		}
  1047  		c.Assert(resealCalls, Equals, tc.resealCalls)
  1048  	}
  1049  }
  1050  
  1051  func (s *secbootSuite) TestSealKeyNoModelParams(c *C) {
  1052  	myKeys := []secboot.SealKeyRequest{
  1053  		{
  1054  			Key:     secboot.EncryptionKey{},
  1055  			KeyFile: "keyfile",
  1056  		},
  1057  	}
  1058  	myParams := secboot.SealKeysParams{
  1059  		TPMPolicyAuthKeyFile: "policy-auth-key-file",
  1060  		TPMLockoutAuthFile:   "lockout-auth-file",
  1061  	}
  1062  
  1063  	err := secboot.SealKeys(myKeys, &myParams)
  1064  	c.Assert(err, ErrorMatches, "at least one set of model-specific parameters is required")
  1065  }
  1066  
  1067  func createMockSnapFile(snapDir, snapPath, snapType string) (snap.Container, error) {
  1068  	snapYamlPath := filepath.Join(snapDir, "meta/snap.yaml")
  1069  	if err := os.MkdirAll(filepath.Dir(snapYamlPath), 0755); err != nil {
  1070  		return nil, err
  1071  	}
  1072  	if err := ioutil.WriteFile(snapYamlPath, []byte("name: foo"), 0644); err != nil {
  1073  		return nil, err
  1074  	}
  1075  	sqfs := squashfs.New(snapPath)
  1076  	if err := sqfs.Build(snapDir, &squashfs.BuildOpts{SnapType: snapType}); err != nil {
  1077  		return nil, err
  1078  	}
  1079  	return snapfile.Open(snapPath)
  1080  }
  1081  
  1082  func mockSbTPMConnection(c *C, tpmErr error) (*sb.TPMConnection, func()) {
  1083  	tcti, err := tpm2.OpenTPMDevice("/dev/null")
  1084  	c.Assert(err, IsNil)
  1085  	tpmctx, err := tpm2.NewTPMContext(tcti)
  1086  	c.Assert(err, IsNil)
  1087  	tpm := &sb.TPMConnection{TPMContext: tpmctx}
  1088  	restore := secboot.MockSbConnectToDefaultTPM(func() (*sb.TPMConnection, error) {
  1089  		if tpmErr != nil {
  1090  			return nil, tpmErr
  1091  		}
  1092  		return tpm, nil
  1093  	})
  1094  	return tpm, restore
  1095  }
  1096  
  1097  func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyBadDisk(c *C) {
  1098  	disk := &disks.MockDiskMapping{
  1099  		FilesystemLabelToPartUUID: map[string]string{},
  1100  	}
  1101  	unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
  1102  	c.Assert(err, ErrorMatches, `filesystem label "ubuntu-save-enc" not found`)
  1103  	c.Check(unlockRes, DeepEquals, secboot.UnlockResult{})
  1104  }
  1105  
  1106  func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyHappy(c *C) {
  1107  	disk := &disks.MockDiskMapping{
  1108  		FilesystemLabelToPartUUID: map[string]string{
  1109  			"ubuntu-save-enc": "123-123-123",
  1110  		},
  1111  	}
  1112  	restore := secboot.MockRandomKernelUUID(func() string {
  1113  		return "random-uuid-123-123"
  1114  	})
  1115  	defer restore()
  1116  	restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte,
  1117  		options *sb.ActivateVolumeOptions) error {
  1118  		c.Check(options, DeepEquals, &sb.ActivateVolumeOptions{})
  1119  		c.Check(key, DeepEquals, []byte("fooo"))
  1120  		c.Check(volumeName, Matches, "ubuntu-save-random-uuid-123-123")
  1121  		c.Check(sourceDevicePath, Equals, "/dev/disk/by-partuuid/123-123-123")
  1122  		return nil
  1123  	})
  1124  	defer restore()
  1125  	unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
  1126  	c.Assert(err, IsNil)
  1127  	c.Check(unlockRes, DeepEquals, secboot.UnlockResult{
  1128  		PartDevice:   "/dev/disk/by-partuuid/123-123-123",
  1129  		FsDevice:     "/dev/mapper/ubuntu-save-random-uuid-123-123",
  1130  		IsEncrypted:  true,
  1131  		UnlockMethod: secboot.UnlockedWithKey,
  1132  	})
  1133  }
  1134  
  1135  func (s *secbootSuite) TestUnlockEncryptedVolumeUsingKeyErr(c *C) {
  1136  	disk := &disks.MockDiskMapping{
  1137  		FilesystemLabelToPartUUID: map[string]string{
  1138  			"ubuntu-save-enc": "123-123-123",
  1139  		},
  1140  	}
  1141  	restore := secboot.MockRandomKernelUUID(func() string {
  1142  		return "random-uuid-123-123"
  1143  	})
  1144  	defer restore()
  1145  	restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte,
  1146  		options *sb.ActivateVolumeOptions) error {
  1147  		return fmt.Errorf("failed")
  1148  	})
  1149  	defer restore()
  1150  	unlockRes, err := secboot.UnlockEncryptedVolumeUsingKey(disk, "ubuntu-save", []byte("fooo"))
  1151  	c.Assert(err, ErrorMatches, "failed")
  1152  	// we would have at least identified that the device is a decrypted one
  1153  	c.Check(unlockRes, DeepEquals, secboot.UnlockResult{
  1154  		IsEncrypted: true,
  1155  		PartDevice:  "/dev/disk/by-partuuid/123-123-123",
  1156  		FsDevice:    "",
  1157  	})
  1158  }
  1159  
  1160  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyErr(c *C) {
  1161  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1162  		return nil, fmt.Errorf("helper error")
  1163  	})
  1164  	defer restore()
  1165  
  1166  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1167  		return true
  1168  	})
  1169  	defer restore()
  1170  
  1171  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1172  		FilesystemLabelToPartUUID: map[string]string{
  1173  			"name-enc": "enc-dev-partuuid",
  1174  		},
  1175  	}
  1176  	defaultDevice := "name"
  1177  	mockSealedKeyFile := makeMockSealedKeyFile(c, nil)
  1178  
  1179  	restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
  1180  		// XXX: this is what the real
  1181  		// MockSbActivateVolumeWithKeyData will do
  1182  		_, _, err := keyData.RecoverKeys()
  1183  		if err != nil {
  1184  			return nil, err
  1185  		}
  1186  		c.Fatal("should not get this far")
  1187  		return nil, nil
  1188  	})
  1189  	defer restore()
  1190  
  1191  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
  1192  	_, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1193  	c.Assert(err, ErrorMatches, `cannot unlock encrypted partition: cannot recover keys because of an unexpected error: cannot run fde-reveal-key "reveal": helper error`)
  1194  }
  1195  
  1196  // this test that v1 hooks and raw binary v1 created sealedKey files still work
  1197  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1AndV1GeneratedSealedKeyFile(c *C) {
  1198  	// The v1 hooks will just return raw bytes. This is deprecated but
  1199  	// we need to keep compatbility with the v1 implementation because
  1200  	// there is a project "denver" that ships with v1 hooks.
  1201  	var reqs []*fde.RevealKeyRequest
  1202  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1203  		reqs = append(reqs, req)
  1204  		return []byte("unsealed-key-64-chars-long-when-not-json-to-match-denver-project"), nil
  1205  	})
  1206  	defer restore()
  1207  
  1208  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1209  		return true
  1210  	})
  1211  	defer restore()
  1212  
  1213  	restore = secboot.MockRandomKernelUUID(func() string {
  1214  		return "random-uuid-for-test"
  1215  	})
  1216  	defer restore()
  1217  
  1218  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1219  		FilesystemLabelToPartUUID: map[string]string{
  1220  			"device-name-enc": "enc-dev-partuuid",
  1221  		},
  1222  	}
  1223  
  1224  	activated := 0
  1225  	restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, options *sb.ActivateVolumeOptions) error {
  1226  		activated++
  1227  		c.Check(string(key), Equals, "unsealed-key-64-chars-long-when-not-json-to-match-denver-project")
  1228  		return nil
  1229  	})
  1230  	defer restore()
  1231  
  1232  	defaultDevice := "device-name"
  1233  	// note that we write a v1 created keyfile here, i.e. it's a raw
  1234  	// disk-key without any json
  1235  	mockSealedKeyFile := filepath.Join(c.MkDir(), "keyfile")
  1236  	sealedKeyContent := []byte("USK$sealed-key-not-json-to-match-denver-project")
  1237  	err := ioutil.WriteFile(mockSealedKeyFile, sealedKeyContent, 0600)
  1238  	c.Assert(err, IsNil)
  1239  
  1240  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
  1241  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1242  	c.Assert(err, IsNil)
  1243  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1244  		UnlockMethod: secboot.UnlockedWithSealedKey,
  1245  		IsEncrypted:  true,
  1246  		PartDevice:   "/dev/disk/by-partuuid/enc-dev-partuuid",
  1247  		FsDevice:     "/dev/mapper/device-name-random-uuid-for-test",
  1248  	})
  1249  	c.Check(activated, Equals, 1)
  1250  	c.Check(reqs, HasLen, 1)
  1251  	c.Check(reqs[0].Op, Equals, "reveal")
  1252  	c.Check(reqs[0].SealedKey, DeepEquals, sealedKeyContent)
  1253  }
  1254  
  1255  func (s *secbootSuite) TestLockSealedKeysCallsFdeReveal(c *C) {
  1256  	var ops []string
  1257  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1258  		ops = append(ops, req.Op)
  1259  		return nil, nil
  1260  	})
  1261  	defer restore()
  1262  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1263  		return true
  1264  	})
  1265  	defer restore()
  1266  
  1267  	err := secboot.LockSealedKeys()
  1268  	c.Assert(err, IsNil)
  1269  
  1270  	c.Check(ops, DeepEquals, []string{"lock"})
  1271  }
  1272  
  1273  func (s *secbootSuite) TestSealKeysWithFDESetupHookHappy(c *C) {
  1274  	tmpdir := c.MkDir()
  1275  
  1276  	n := 0
  1277  	sealedPrefix := []byte("SEALED:")
  1278  	rawHandle1 := json.RawMessage(`{"handle-for":"key1"}`)
  1279  	var runFDESetupHookReqs []*fde.SetupRequest
  1280  	runFDESetupHook := func(req *fde.SetupRequest) ([]byte, error) {
  1281  		n++
  1282  		runFDESetupHookReqs = append(runFDESetupHookReqs, req)
  1283  		payload := append(sealedPrefix, req.Key...)
  1284  		var handle *json.RawMessage
  1285  		if req.KeyName == "key1" {
  1286  			handle = &rawHandle1
  1287  		}
  1288  		res := &fde.InitialSetupResult{
  1289  			EncryptedKey: payload,
  1290  			Handle:       handle,
  1291  		}
  1292  		return json.Marshal(res)
  1293  	}
  1294  
  1295  	key1 := secboot.EncryptionKey{1, 2, 3, 4}
  1296  	key2 := secboot.EncryptionKey{5, 6, 7, 8}
  1297  	auxKey := secboot.AuxKey{9, 10, 11, 12}
  1298  	key1Fn := filepath.Join(tmpdir, "key1.key")
  1299  	key2Fn := filepath.Join(tmpdir, "key2.key")
  1300  	auxKeyFn := filepath.Join(tmpdir, "aux-key")
  1301  	params := secboot.SealKeysWithFDESetupHookParams{
  1302  		Model:      fakeModel,
  1303  		AuxKey:     auxKey,
  1304  		AuxKeyFile: auxKeyFn,
  1305  	}
  1306  	err := secboot.SealKeysWithFDESetupHook(runFDESetupHook,
  1307  		[]secboot.SealKeyRequest{
  1308  			{Key: key1, KeyName: "key1", KeyFile: key1Fn},
  1309  			{Key: key2, KeyName: "key2", KeyFile: key2Fn},
  1310  		}, &params)
  1311  	c.Assert(err, IsNil)
  1312  	// check that runFDESetupHook was called the expected way
  1313  	key1Payload := sb.MarshalKeys([]byte(key1), auxKey[:])
  1314  	key2Payload := sb.MarshalKeys([]byte(key2), auxKey[:])
  1315  	c.Check(runFDESetupHookReqs, DeepEquals, []*fde.SetupRequest{
  1316  		{Op: "initial-setup", Key: key1Payload, KeyName: "key1"},
  1317  		{Op: "initial-setup", Key: key2Payload, KeyName: "key2"},
  1318  	})
  1319  	// check that the sealed keys got written to the expected places
  1320  	for _, p := range []string{key1Fn, key2Fn} {
  1321  		c.Check(p, testutil.FilePresent)
  1322  	}
  1323  	c.Check(auxKeyFn, testutil.FileEquals, auxKey[:])
  1324  
  1325  	// roundtrip to check what was written
  1326  	s.checkV2Key(c, key1Fn, sealedPrefix, key1, auxKey[:], fakeModel, &rawHandle1)
  1327  	nullHandle := json.RawMessage("null")
  1328  	s.checkV2Key(c, key2Fn, sealedPrefix, key2, auxKey[:], fakeModel, &nullHandle)
  1329  }
  1330  
  1331  func (s *secbootSuite) TestSealKeysWithFDESetupHookSad(c *C) {
  1332  	tmpdir := c.MkDir()
  1333  
  1334  	runFDESetupHook := func(req *fde.SetupRequest) ([]byte, error) {
  1335  		return nil, fmt.Errorf("hook failed")
  1336  	}
  1337  
  1338  	key := secboot.EncryptionKey{1, 2, 3, 4}
  1339  	auxKey := secboot.AuxKey{5, 6, 7, 8}
  1340  	keyFn := filepath.Join(tmpdir, "key.key")
  1341  	auxKeyFn := filepath.Join(tmpdir, "aux-key")
  1342  	params := secboot.SealKeysWithFDESetupHookParams{
  1343  		Model:      fakeModel,
  1344  		AuxKey:     auxKey,
  1345  		AuxKeyFile: auxKeyFn,
  1346  	}
  1347  	err := secboot.SealKeysWithFDESetupHook(runFDESetupHook,
  1348  		[]secboot.SealKeyRequest{
  1349  			{Key: key, KeyName: "key1", KeyFile: keyFn},
  1350  		}, &params)
  1351  	c.Assert(err, ErrorMatches, "hook failed")
  1352  	c.Check(keyFn, testutil.FileAbsent)
  1353  	c.Check(auxKeyFn, testutil.FileAbsent)
  1354  }
  1355  
  1356  func makeMockDiskKey() secboot.EncryptionKey {
  1357  	return secboot.EncryptionKey{0, 1, 2, 3, 4, 5}
  1358  }
  1359  
  1360  func makeMockAuxKey() secboot.AuxKey {
  1361  	return secboot.AuxKey{6, 7, 8, 9}
  1362  }
  1363  
  1364  func makeMockUnencryptedPayload() []byte {
  1365  	diskKey := makeMockDiskKey()
  1366  	auxKey := makeMockAuxKey()
  1367  	return sb.MarshalKeys([]byte(diskKey), auxKey[:])
  1368  }
  1369  
  1370  func makeMockEncryptedPayload() []byte {
  1371  	pl := makeMockUnencryptedPayload()
  1372  	// rot13 ftw
  1373  	for i := range pl {
  1374  		pl[i] = pl[i] ^ 0x13
  1375  	}
  1376  	return pl
  1377  }
  1378  
  1379  func makeMockEncryptedPayloadString() string {
  1380  	return base64.StdEncoding.EncodeToString(makeMockEncryptedPayload())
  1381  }
  1382  
  1383  func makeMockSealedKeyFile(c *C, handle json.RawMessage) string {
  1384  	mockSealedKeyFile := filepath.Join(c.MkDir(), "keyfile")
  1385  	var handleJSON string
  1386  	if len(handle) != 0 {
  1387  		handleJSON = fmt.Sprintf(`"platform_handle":%s,`, handle)
  1388  	}
  1389  	sealedKeyContent := fmt.Sprintf(`{"platform_name":"fde-hook-v2",%s"encrypted_payload":"%s"}`, handleJSON, makeMockEncryptedPayloadString())
  1390  	err := ioutil.WriteFile(mockSealedKeyFile, []byte(sealedKeyContent), 0600)
  1391  	c.Assert(err, IsNil)
  1392  	return mockSealedKeyFile
  1393  }
  1394  
  1395  var fakeModel = assertstest.FakeAssertion(map[string]interface{}{
  1396  	"type":         "model",
  1397  	"authority-id": "my-brand",
  1398  	"series":       "16",
  1399  	"brand-id":     "my-brand",
  1400  	"model":        "my-model",
  1401  	"grade":        "signed",
  1402  	"architecture": "amd64",
  1403  	"base":         "core20",
  1404  	"snaps": []interface{}{
  1405  		map[string]interface{}{
  1406  			"name":            "pc-kernel",
  1407  			"id":              "pYVQrBcKmBa0mZ4CCN7ExT6jH8rY1hza",
  1408  			"type":            "kernel",
  1409  			"default-channel": "20",
  1410  		},
  1411  		map[string]interface{}{
  1412  			"name":            "pc",
  1413  			"id":              "UqFziVZDHLSyO3TqSWgNBoAdHbLI4dAH",
  1414  			"type":            "gadget",
  1415  			"default-channel": "20",
  1416  		}},
  1417  }).(*asserts.Model)
  1418  
  1419  type mockSnapModelChecker struct {
  1420  	mockIsAuthorized bool
  1421  	mockError        error
  1422  }
  1423  
  1424  func (c *mockSnapModelChecker) IsModelAuthorized(model sb.SnapModel) (bool, error) {
  1425  	if model.BrandID() != "my-brand" || model.Model() != "my-model" {
  1426  		return false, fmt.Errorf("not the test model")
  1427  	}
  1428  	return c.mockIsAuthorized, c.mockError
  1429  }
  1430  func (c *mockSnapModelChecker) VolumeName() string {
  1431  	return "volume-name"
  1432  }
  1433  
  1434  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2(c *C) {
  1435  	var reqs []*fde.RevealKeyRequest
  1436  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1437  		reqs = append(reqs, req)
  1438  		return []byte(fmt.Sprintf(`{"key": "%s"}`, base64.StdEncoding.EncodeToString(makeMockUnencryptedPayload()))), nil
  1439  	})
  1440  	defer restore()
  1441  
  1442  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1443  		return true
  1444  	})
  1445  	defer restore()
  1446  
  1447  	restore = secboot.MockRandomKernelUUID(func() string {
  1448  		return "random-uuid-for-test"
  1449  	})
  1450  	defer restore()
  1451  
  1452  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1453  		FilesystemLabelToPartUUID: map[string]string{
  1454  			"device-name-enc": "enc-dev-partuuid",
  1455  		},
  1456  	}
  1457  
  1458  	expectedKey := makeMockDiskKey()
  1459  	expectedAuxKey := makeMockAuxKey()
  1460  	activated := 0
  1461  	restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
  1462  		activated++
  1463  		c.Check(options.RecoveryKeyTries, Equals, 0)
  1464  		// XXX: this is what the real
  1465  		// MockSbActivateVolumeWithKeyData will do
  1466  		key, auxKey, err := keyData.RecoverKeys()
  1467  		c.Assert(err, IsNil)
  1468  		c.Check([]byte(key), DeepEquals, []byte(expectedKey))
  1469  		c.Check([]byte(auxKey), DeepEquals, expectedAuxKey[:])
  1470  		modChecker := &mockSnapModelChecker{mockIsAuthorized: true}
  1471  		return modChecker, nil
  1472  	})
  1473  	defer restore()
  1474  
  1475  	defaultDevice := "device-name"
  1476  	handle := json.RawMessage(`{"a": "handle"}`)
  1477  	mockSealedKeyFile := makeMockSealedKeyFile(c, handle)
  1478  
  1479  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
  1480  		WhichModel: func() (*asserts.Model, error) {
  1481  			return fakeModel, nil
  1482  		},
  1483  	}
  1484  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1485  	c.Assert(err, IsNil)
  1486  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1487  		UnlockMethod: secboot.UnlockedWithSealedKey,
  1488  		IsEncrypted:  true,
  1489  		PartDevice:   "/dev/disk/by-partuuid/enc-dev-partuuid",
  1490  		FsDevice:     "/dev/mapper/device-name-random-uuid-for-test",
  1491  	})
  1492  	c.Check(activated, Equals, 1)
  1493  	c.Check(reqs, HasLen, 1)
  1494  	c.Check(reqs[0].Op, Equals, "reveal")
  1495  	c.Check(reqs[0].SealedKey, DeepEquals, makeMockEncryptedPayload())
  1496  	c.Check(reqs[0].Handle, DeepEquals, &handle)
  1497  }
  1498  
  1499  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2ModelUnauthorized(c *C) {
  1500  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1501  		return true
  1502  	})
  1503  	defer restore()
  1504  
  1505  	restore = secboot.MockRandomKernelUUID(func() string {
  1506  		return "random-uuid-for-test"
  1507  	})
  1508  	defer restore()
  1509  
  1510  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1511  		FilesystemLabelToPartUUID: map[string]string{
  1512  			"device-name-enc": "enc-dev-partuuid",
  1513  		},
  1514  	}
  1515  
  1516  	activated := 0
  1517  	restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
  1518  		activated++
  1519  		modChecker := &mockSnapModelChecker{mockIsAuthorized: false}
  1520  		return modChecker, nil
  1521  	})
  1522  	defer restore()
  1523  
  1524  	deactivated := 0
  1525  	restore = secboot.MockSbDeactivateVolume(func(volumeName string) error {
  1526  		deactivated++
  1527  		c.Check(volumeName, Equals, "device-name-random-uuid-for-test")
  1528  		return nil
  1529  	})
  1530  	defer restore()
  1531  
  1532  	defaultDevice := "device-name"
  1533  	handle := json.RawMessage(`{"a": "handle"}`)
  1534  	mockSealedKeyFile := makeMockSealedKeyFile(c, handle)
  1535  
  1536  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
  1537  		WhichModel: func() (*asserts.Model, error) {
  1538  			return fakeModel, nil
  1539  		},
  1540  	}
  1541  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1542  	c.Assert(err, ErrorMatches, `cannot unlock volume: model my-brand/my-model not authorized`)
  1543  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1544  		IsEncrypted: true,
  1545  		PartDevice:  "/dev/disk/by-partuuid/enc-dev-partuuid",
  1546  	})
  1547  	c.Check(activated, Equals, 1)
  1548  	c.Check(deactivated, Equals, 1)
  1549  }
  1550  
  1551  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2ModelCheckerError(c *C) {
  1552  	restore := secboot.MockFDEHasRevealKey(func() bool {
  1553  		return true
  1554  	})
  1555  	defer restore()
  1556  
  1557  	restore = secboot.MockRandomKernelUUID(func() string {
  1558  		return "random-uuid-for-test"
  1559  	})
  1560  	defer restore()
  1561  
  1562  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1563  		FilesystemLabelToPartUUID: map[string]string{
  1564  			"device-name-enc": "enc-dev-partuuid",
  1565  		},
  1566  	}
  1567  
  1568  	activated := 0
  1569  	restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
  1570  		activated++
  1571  		modChecker := &mockSnapModelChecker{mockError: errors.New("model checker error")}
  1572  		return modChecker, nil
  1573  	})
  1574  	defer restore()
  1575  
  1576  	defaultDevice := "device-name"
  1577  	handle := json.RawMessage(`{"a": "handle"}`)
  1578  	mockSealedKeyFile := makeMockSealedKeyFile(c, handle)
  1579  
  1580  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
  1581  		WhichModel: func() (*asserts.Model, error) {
  1582  			return fakeModel, nil
  1583  		},
  1584  	}
  1585  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1586  	c.Assert(err, ErrorMatches, `cannot check if model is authorized to unlock disk: model checker error`)
  1587  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1588  		IsEncrypted: true,
  1589  		PartDevice:  "/dev/disk/by-partuuid/enc-dev-partuuid",
  1590  	})
  1591  	c.Check(activated, Equals, 1)
  1592  }
  1593  
  1594  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV2AllowRecoverKey(c *C) {
  1595  	var reqs []*fde.RevealKeyRequest
  1596  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1597  		reqs = append(reqs, req)
  1598  		return []byte("invalid-json"), nil
  1599  	})
  1600  	defer restore()
  1601  
  1602  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1603  		return true
  1604  	})
  1605  	defer restore()
  1606  
  1607  	restore = secboot.MockRandomKernelUUID(func() string {
  1608  		return "random-uuid-for-test"
  1609  	})
  1610  	defer restore()
  1611  
  1612  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1613  		FilesystemLabelToPartUUID: map[string]string{
  1614  			"device-name-enc": "enc-dev-partuuid",
  1615  		},
  1616  	}
  1617  
  1618  	activated := 0
  1619  	restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
  1620  		activated++
  1621  		c.Check(options.RecoveryKeyTries, Equals, 3)
  1622  		// XXX: this is what the real
  1623  		// MockSbActivateVolumeWithKeyData will do
  1624  		_, _, err := keyData.RecoverKeys()
  1625  		c.Assert(err, NotNil)
  1626  		return nil, sb.ErrRecoveryKeyUsed
  1627  	})
  1628  	defer restore()
  1629  
  1630  	defaultDevice := "device-name"
  1631  	handle := json.RawMessage(`{"a": "handle"}`)
  1632  	mockSealedKeyFile := makeMockSealedKeyFile(c, handle)
  1633  
  1634  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{AllowRecoveryKey: true}
  1635  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1636  	c.Assert(err, IsNil)
  1637  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1638  		UnlockMethod: secboot.UnlockedWithRecoveryKey,
  1639  		IsEncrypted:  true,
  1640  		PartDevice:   "/dev/disk/by-partuuid/enc-dev-partuuid",
  1641  		FsDevice:     "/dev/mapper/device-name-random-uuid-for-test",
  1642  	})
  1643  	c.Check(activated, Equals, 1)
  1644  	c.Check(reqs, HasLen, 1)
  1645  	c.Check(reqs[0].Op, Equals, "reveal")
  1646  	c.Check(reqs[0].SealedKey, DeepEquals, makeMockEncryptedPayload())
  1647  	c.Check(reqs[0].Handle, DeepEquals, &handle)
  1648  }
  1649  
  1650  func (s *secbootSuite) checkV2Key(c *C, keyFn string, prefixToDrop, expectedKey, expectedAuxKey []byte, authModel *asserts.Model, handle *json.RawMessage) {
  1651  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1652  		c.Check(req.Handle, DeepEquals, handle)
  1653  		c.Check(bytes.HasPrefix(req.SealedKey, prefixToDrop), Equals, true)
  1654  		payload := req.SealedKey[len(prefixToDrop):]
  1655  		return []byte(fmt.Sprintf(`{"key": "%s"}`, base64.StdEncoding.EncodeToString(payload))), nil
  1656  	})
  1657  	defer restore()
  1658  
  1659  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1660  		return true
  1661  	})
  1662  	defer restore()
  1663  
  1664  	restore = secboot.MockRandomKernelUUID(func() string {
  1665  		return "random-uuid-for-test"
  1666  	})
  1667  	defer restore()
  1668  
  1669  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1670  		FilesystemLabelToPartUUID: map[string]string{
  1671  			"device-name-enc": "enc-dev-partuuid",
  1672  		},
  1673  	}
  1674  
  1675  	activated := 0
  1676  	restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
  1677  		activated++
  1678  		// XXX: this is what the real
  1679  		// MockSbActivateVolumeWithKeyData will do
  1680  		key, auxKey, err := keyData.RecoverKeys()
  1681  		c.Assert(err, IsNil)
  1682  		c.Check([]byte(key), DeepEquals, []byte(expectedKey))
  1683  		c.Check([]byte(auxKey), DeepEquals, []byte(expectedAuxKey))
  1684  		// check against model
  1685  		ok, err := keyData.IsSnapModelAuthorized(auxKey, authModel)
  1686  		c.Assert(err, IsNil)
  1687  		c.Check(ok, Equals, true)
  1688  		modChecker := &mockSnapModelChecker{mockIsAuthorized: true}
  1689  		return modChecker, nil
  1690  	})
  1691  	defer restore()
  1692  
  1693  	defaultDevice := "device-name"
  1694  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{
  1695  		WhichModel: func() (*asserts.Model, error) {
  1696  			return fakeModel, nil
  1697  		},
  1698  	}
  1699  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, keyFn, opts)
  1700  	c.Assert(err, IsNil)
  1701  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1702  		UnlockMethod: secboot.UnlockedWithSealedKey,
  1703  		IsEncrypted:  true,
  1704  		PartDevice:   "/dev/disk/by-partuuid/enc-dev-partuuid",
  1705  		FsDevice:     "/dev/mapper/device-name-random-uuid-for-test",
  1706  	})
  1707  	c.Check(activated, Equals, 1)
  1708  }
  1709  
  1710  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyV1(c *C) {
  1711  	mockDiskKey := []byte("unsealed-key--64-chars-long-and-not-json-to-match-denver-project")
  1712  	c.Assert(len(mockDiskKey), Equals, 64)
  1713  
  1714  	var reqs []*fde.RevealKeyRequest
  1715  	// The v1 hooks will just return raw bytes. This is deprecated but
  1716  	// we need to keep compatbility with the v1 implementation because
  1717  	// there is a project "denver" that ships with v1 hooks.
  1718  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1719  		reqs = append(reqs, req)
  1720  		return mockDiskKey, nil
  1721  	})
  1722  	defer restore()
  1723  
  1724  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1725  		return true
  1726  	})
  1727  	defer restore()
  1728  
  1729  	restore = secboot.MockRandomKernelUUID(func() string {
  1730  		return "random-uuid-for-test"
  1731  	})
  1732  	defer restore()
  1733  
  1734  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1735  		FilesystemLabelToPartUUID: map[string]string{
  1736  			"device-name-enc": "enc-dev-partuuid",
  1737  		},
  1738  	}
  1739  
  1740  	mockEncryptedDiskKey := []byte("USK$encrypted-key-no-json-to-match-denver-project")
  1741  	activated := 0
  1742  	restore = secboot.MockSbActivateVolumeWithKey(func(volumeName, sourceDevicePath string, key []byte, options *sb.ActivateVolumeOptions) error {
  1743  		activated++
  1744  		c.Check(key, DeepEquals, mockDiskKey)
  1745  		return nil
  1746  	})
  1747  	defer restore()
  1748  
  1749  	defaultDevice := "device-name"
  1750  	// note that we write a v1 created keyfile here, i.e. it's a raw
  1751  	// disk-key without any json
  1752  	mockSealedKeyFile := filepath.Join(c.MkDir(), "keyfile")
  1753  	err := ioutil.WriteFile(mockSealedKeyFile, mockEncryptedDiskKey, 0600)
  1754  	c.Assert(err, IsNil)
  1755  
  1756  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
  1757  	res, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1758  	c.Assert(err, IsNil)
  1759  	c.Check(res, DeepEquals, secboot.UnlockResult{
  1760  		UnlockMethod: secboot.UnlockedWithSealedKey,
  1761  		IsEncrypted:  true,
  1762  		PartDevice:   "/dev/disk/by-partuuid/enc-dev-partuuid",
  1763  		FsDevice:     "/dev/mapper/device-name-random-uuid-for-test",
  1764  	})
  1765  	c.Check(activated, Equals, 1)
  1766  	c.Check(reqs, HasLen, 1)
  1767  	c.Check(reqs[0].Op, Equals, "reveal")
  1768  	c.Check(reqs[0].SealedKey, DeepEquals, mockEncryptedDiskKey)
  1769  	c.Check(reqs[0].Handle, IsNil)
  1770  }
  1771  
  1772  func (s *secbootSuite) TestUnlockVolumeUsingSealedKeyIfEncryptedFdeRevealKeyBadJSONv2(c *C) {
  1773  	restore := fde.MockRunFDERevealKey(func(req *fde.RevealKeyRequest) ([]byte, error) {
  1774  		return []byte("invalid-json"), nil
  1775  	})
  1776  	defer restore()
  1777  
  1778  	restore = secboot.MockFDEHasRevealKey(func() bool {
  1779  		return true
  1780  	})
  1781  	defer restore()
  1782  
  1783  	restore = secboot.MockRandomKernelUUID(func() string {
  1784  		return "random-uuid-for-test"
  1785  	})
  1786  	defer restore()
  1787  
  1788  	mockDiskWithEncDev := &disks.MockDiskMapping{
  1789  		FilesystemLabelToPartUUID: map[string]string{
  1790  			"device-name-enc": "enc-dev-partuuid",
  1791  		},
  1792  	}
  1793  
  1794  	restore = secboot.MockSbActivateVolumeWithKeyData(func(volumeName, sourceDevicePath string, keyData *sb.KeyData, options *sb.ActivateVolumeOptions) (sb.SnapModelChecker, error) {
  1795  		// XXX: this is what the real
  1796  		// MockSbActivateVolumeWithKeyData will do
  1797  		_, _, err := keyData.RecoverKeys()
  1798  		if err != nil {
  1799  			return nil, err
  1800  		}
  1801  		c.Fatal("should not get this far")
  1802  		return nil, nil
  1803  	})
  1804  	defer restore()
  1805  
  1806  	defaultDevice := "device-name"
  1807  	mockSealedKeyFile := makeMockSealedKeyFile(c, nil)
  1808  
  1809  	opts := &secboot.UnlockVolumeUsingSealedKeyOptions{}
  1810  	_, err := secboot.UnlockVolumeUsingSealedKeyIfEncrypted(mockDiskWithEncDev, defaultDevice, mockSealedKeyFile, opts)
  1811  
  1812  	c.Check(err, ErrorMatches, `cannot unlock encrypted partition: invalid key data:.*`)
  1813  }