github.com/meulengracht/snapd@v0.0.0-20210719210640-8bde69bcc84e/secboot/secboot_tpm.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
    22  
    23  import (
    24  	"crypto/rand"
    25  	"errors"
    26  	"fmt"
    27  	"io/ioutil"
    28  	"os"
    29  
    30  	"github.com/canonical/go-tpm2"
    31  	"github.com/snapcore/secboot"
    32  	sb "github.com/snapcore/secboot"
    33  	"golang.org/x/xerrors"
    34  
    35  	"github.com/snapcore/snapd/asserts"
    36  	"github.com/snapcore/snapd/bootloader"
    37  	"github.com/snapcore/snapd/bootloader/efi"
    38  	"github.com/snapcore/snapd/logger"
    39  	"github.com/snapcore/snapd/osutil"
    40  	"github.com/snapcore/snapd/randutil"
    41  	"github.com/snapcore/snapd/snap/snapfile"
    42  )
    43  
    44  const (
    45  	keyringPrefix = "ubuntu-fde"
    46  )
    47  
    48  var (
    49  	sbConnectToDefaultTPM                  = sb.ConnectToDefaultTPM
    50  	sbMeasureSnapSystemEpochToTPM          = sb.MeasureSnapSystemEpochToTPM
    51  	sbMeasureSnapModelToTPM                = sb.MeasureSnapModelToTPM
    52  	sbBlockPCRProtectionPolicies           = sb.BlockPCRProtectionPolicies
    53  	sbActivateVolumeWithTPMSealedKey       = sb.ActivateVolumeWithTPMSealedKey
    54  	sbAddEFISecureBootPolicyProfile        = sb.AddEFISecureBootPolicyProfile
    55  	sbAddEFIBootManagerProfile             = sb.AddEFIBootManagerProfile
    56  	sbAddSystemdEFIStubProfile             = sb.AddSystemdEFIStubProfile
    57  	sbAddSnapModelProfile                  = sb.AddSnapModelProfile
    58  	sbSealKeyToTPMMultiple                 = sb.SealKeyToTPMMultiple
    59  	sbUpdateKeyPCRProtectionPolicyMultiple = sb.UpdateKeyPCRProtectionPolicyMultiple
    60  
    61  	randutilRandomKernelUUID = randutil.RandomKernelUUID
    62  
    63  	isTPMEnabled = isTPMEnabledImpl
    64  	provisionTPM = provisionTPMImpl
    65  
    66  	// dummy to check whether the interfaces match
    67  	_ (secboot.SnapModel) = ModelForSealing(nil)
    68  )
    69  
    70  func isTPMEnabledImpl(tpm *sb.TPMConnection) bool {
    71  	return tpm.IsEnabled()
    72  }
    73  
    74  func CheckTPMKeySealingSupported() error {
    75  	logger.Noticef("checking if secure boot is enabled...")
    76  	if err := checkSecureBootEnabled(); err != nil {
    77  		logger.Noticef("secure boot not enabled: %v", err)
    78  		return err
    79  	}
    80  	logger.Noticef("secure boot is enabled")
    81  
    82  	logger.Noticef("checking if TPM device is available...")
    83  	tpm, err := sbConnectToDefaultTPM()
    84  	if err != nil {
    85  		err = fmt.Errorf("cannot connect to TPM device: %v", err)
    86  		logger.Noticef("%v", err)
    87  		return err
    88  	}
    89  	defer tpm.Close()
    90  
    91  	if !isTPMEnabled(tpm) {
    92  		logger.Noticef("TPM device detected but not enabled")
    93  		return fmt.Errorf("TPM device is not enabled")
    94  	}
    95  
    96  	logger.Noticef("TPM device detected and enabled")
    97  
    98  	return nil
    99  }
   100  
   101  func checkSecureBootEnabled() error {
   102  	// 8be4df61-93ca-11d2-aa0d-00e098032b8c is the EFI Global Variable vendor GUID
   103  	b, _, err := efi.ReadVarBytes("SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c")
   104  	if err != nil {
   105  		if err == efi.ErrNoEFISystem {
   106  			return err
   107  		}
   108  		return fmt.Errorf("cannot read secure boot variable: %v", err)
   109  	}
   110  	if len(b) < 1 {
   111  		return errors.New("secure boot variable does not exist")
   112  	}
   113  	if b[0] != 1 {
   114  		return errors.New("secure boot is disabled")
   115  	}
   116  
   117  	return nil
   118  }
   119  
   120  // initramfsPCR is the TPM PCR that we reserve for the EFI image and use
   121  // for measurement from the initramfs.
   122  const initramfsPCR = 12
   123  
   124  func secureConnectToTPM(ekcfile string) (*sb.TPMConnection, error) {
   125  	ekCertReader, err := os.Open(ekcfile)
   126  	if err != nil {
   127  		return nil, fmt.Errorf("cannot open endorsement key certificate file: %v", err)
   128  	}
   129  	defer ekCertReader.Close()
   130  
   131  	return sb.SecureConnectToDefaultTPM(ekCertReader, nil)
   132  }
   133  
   134  func insecureConnectToTPM() (*sb.TPMConnection, error) {
   135  	return sbConnectToDefaultTPM()
   136  }
   137  
   138  func measureWhenPossible(whatHow func(tpm *sb.TPMConnection) error) error {
   139  	// the model is ready, we're good to try measuring it now
   140  	tpm, err := insecureConnectToTPM()
   141  	if err != nil {
   142  		if xerrors.Is(err, sb.ErrNoTPM2Device) {
   143  			return nil
   144  		}
   145  		return fmt.Errorf("cannot open TPM connection: %v", err)
   146  	}
   147  	defer tpm.Close()
   148  
   149  	if !isTPMEnabled(tpm) {
   150  		return nil
   151  	}
   152  
   153  	return whatHow(tpm)
   154  }
   155  
   156  // MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the
   157  // TPM device is available. If there's no TPM device success is returned.
   158  func MeasureSnapSystemEpochWhenPossible() error {
   159  	measure := func(tpm *sb.TPMConnection) error {
   160  		return sbMeasureSnapSystemEpochToTPM(tpm, initramfsPCR)
   161  	}
   162  
   163  	if err := measureWhenPossible(measure); err != nil {
   164  		return fmt.Errorf("cannot measure snap system epoch: %v", err)
   165  	}
   166  
   167  	return nil
   168  }
   169  
   170  // MeasureSnapModelWhenPossible measures the snap model only if the TPM device is
   171  // available. If there's no TPM device success is returned.
   172  func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error {
   173  	measure := func(tpm *sb.TPMConnection) error {
   174  		model, err := findModel()
   175  		if err != nil {
   176  			return err
   177  		}
   178  		return sbMeasureSnapModelToTPM(tpm, initramfsPCR, model)
   179  	}
   180  
   181  	if err := measureWhenPossible(measure); err != nil {
   182  		return fmt.Errorf("cannot measure snap model: %v", err)
   183  	}
   184  
   185  	return nil
   186  }
   187  
   188  func lockTPMSealedKeys() error {
   189  	tpm, tpmErr := sbConnectToDefaultTPM()
   190  	if tpmErr != nil {
   191  		if xerrors.Is(tpmErr, sb.ErrNoTPM2Device) {
   192  			logger.Noticef("cannot open TPM connection: %v", tpmErr)
   193  			return nil
   194  		}
   195  		return fmt.Errorf("cannot lock TPM: %v", tpmErr)
   196  	}
   197  	defer tpm.Close()
   198  
   199  	// Lock access to the sealed keys. This should be called whenever there
   200  	// is a TPM device detected, regardless of whether secure boot is enabled
   201  	// or there is an encrypted volume to unlock. Note that snap-bootstrap can
   202  	// be called several times during initialization, and if there are multiple
   203  	// volumes to unlock we should lock access to the sealed keys only after
   204  	// the last encrypted volume is unlocked, in which case lockKeysOnFinish
   205  	// should be set to true.
   206  	//
   207  	// We should only touch the PCR that we've currently reserved for the kernel
   208  	// EFI image. Touching others will break the ability to perform any kind of
   209  	// attestation using the TPM because it will make the log inconsistent.
   210  	return sbBlockPCRProtectionPolicies(tpm, []int{initramfsPCR})
   211  }
   212  
   213  func unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) {
   214  	// TODO:UC20: use sb.SecureConnectToDefaultTPM() if we decide there's benefit in doing that or
   215  	//            we have a hard requirement for a valid EK cert chain for every boot (ie, panic
   216  	//            if there isn't one). But we can't do that as long as we need to download
   217  	//            intermediate certs from the manufacturer.
   218  
   219  	res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice}
   220  	// Obtain a TPM connection.
   221  	tpm, tpmErr := sbConnectToDefaultTPM()
   222  	if tpmErr != nil {
   223  		if !xerrors.Is(tpmErr, sb.ErrNoTPM2Device) {
   224  			return res, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr)
   225  		}
   226  		logger.Noticef("cannot open TPM connection: %v", tpmErr)
   227  	} else {
   228  		defer tpm.Close()
   229  	}
   230  
   231  	// Also check if the TPM device is enabled. The platform firmware may disable the storage
   232  	// and endorsement hierarchies, but the device will remain visible to the operating system.
   233  	tpmDeviceAvailable := tpmErr == nil && isTPMEnabled(tpm)
   234  
   235  	// if we don't have a tpm, and we allow using a recovery key, do that
   236  	// directly
   237  	if !tpmDeviceAvailable && opts.AllowRecoveryKey {
   238  		if err := UnlockEncryptedVolumeWithRecoveryKey(mapperName, sourceDevice); err != nil {
   239  			return res, err
   240  		}
   241  		res.FsDevice = targetDevice
   242  		res.UnlockMethod = UnlockedWithRecoveryKey
   243  		return res, nil
   244  	}
   245  
   246  	// otherwise we have a tpm and we should use the sealed key first, but
   247  	// this method will fallback to using the recovery key if enabled
   248  	method, err := unlockEncryptedPartitionWithSealedKey(tpm, mapperName, sourceDevice, sealedEncryptionKeyFile, "", opts.AllowRecoveryKey)
   249  	res.UnlockMethod = method
   250  	if err == nil {
   251  		res.FsDevice = targetDevice
   252  	}
   253  	return res, err
   254  }
   255  
   256  func isActivatedWithRecoveryKey(err error) bool {
   257  	if err == nil {
   258  		return false
   259  	}
   260  	// with non-nil err, we should check for err being ActivateWithTPMSealedKeyError
   261  	// and RecoveryKeyUsageErr inside that being nil - this indicates that the
   262  	// recovery key was used to unlock it
   263  	activateErr, ok := err.(*sb.ActivateWithTPMSealedKeyError)
   264  	if !ok {
   265  		return false
   266  	}
   267  	return activateErr.RecoveryKeyUsageErr == nil
   268  }
   269  
   270  func activateVolOpts(allowRecoveryKey bool) *sb.ActivateVolumeOptions {
   271  	options := sb.ActivateVolumeOptions{
   272  		PassphraseTries: 1,
   273  		// disable recovery key by default
   274  		RecoveryKeyTries: 0,
   275  		KeyringPrefix:    keyringPrefix,
   276  	}
   277  	if allowRecoveryKey {
   278  		// enable recovery key only when explicitly allowed
   279  		options.RecoveryKeyTries = 3
   280  	}
   281  	return &options
   282  }
   283  
   284  // unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted
   285  // device. If activation with the sealed key fails, this function will attempt to
   286  // activate it with the fallback recovery key instead.
   287  func unlockEncryptedPartitionWithSealedKey(tpm *sb.TPMConnection, name, device, keyfile, pinfile string, allowRecovery bool) (UnlockMethod, error) {
   288  	options := activateVolOpts(allowRecovery)
   289  	// XXX: pinfile is currently not used
   290  	activated, err := sbActivateVolumeWithTPMSealedKey(tpm, name, device, keyfile, nil, options)
   291  
   292  	if activated {
   293  		// non nil error may indicate the volume was unlocked using the
   294  		// recovery key
   295  		if err == nil {
   296  			logger.Noticef("successfully activated encrypted device %q with TPM", device)
   297  			return UnlockedWithSealedKey, nil
   298  		} else if isActivatedWithRecoveryKey(err) {
   299  			logger.Noticef("successfully activated encrypted device %q using a fallback activation method", device)
   300  			return UnlockedWithRecoveryKey, nil
   301  		}
   302  		// no other error is possible when activation succeeded
   303  		return UnlockStatusUnknown, fmt.Errorf("internal error: volume activated with unexpected error: %v", err)
   304  	}
   305  	// ActivateVolumeWithTPMSealedKey should always return an error if activated == false
   306  	return NotUnlocked, fmt.Errorf("cannot activate encrypted device %q: %v", device, err)
   307  }
   308  
   309  // SealKeys provisions the TPM and seals the encryption keys according to the
   310  // specified parameters. If the TPM is already provisioned, or a sealed key already
   311  // exists, SealKeys will fail and return an error.
   312  func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error {
   313  	numModels := len(params.ModelParams)
   314  	if numModels < 1 {
   315  		return fmt.Errorf("at least one set of model-specific parameters is required")
   316  	}
   317  
   318  	tpm, err := sbConnectToDefaultTPM()
   319  	if err != nil {
   320  		return fmt.Errorf("cannot connect to TPM: %v", err)
   321  	}
   322  	defer tpm.Close()
   323  	if !isTPMEnabled(tpm) {
   324  		return fmt.Errorf("TPM device is not enabled")
   325  	}
   326  
   327  	pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
   328  	if err != nil {
   329  		return err
   330  	}
   331  
   332  	if params.TPMProvision {
   333  		// Provision the TPM as late as possible
   334  		if err := tpmProvision(tpm, params.TPMLockoutAuthFile); err != nil {
   335  			return err
   336  		}
   337  	}
   338  
   339  	// Seal the provided keys to the TPM
   340  	creationParams := sb.KeyCreationParams{
   341  		PCRProfile:             pcrProfile,
   342  		PCRPolicyCounterHandle: tpm2.Handle(params.PCRPolicyCounterHandle),
   343  		AuthKey:                params.TPMPolicyAuthKey,
   344  	}
   345  
   346  	sbKeys := make([]*sb.SealKeyRequest, 0, len(keys))
   347  	for i := range keys {
   348  		sbKeys = append(sbKeys, &sb.SealKeyRequest{
   349  			Key:  keys[i].Key,
   350  			Path: keys[i].KeyFile,
   351  		})
   352  	}
   353  
   354  	authKey, err := sbSealKeyToTPMMultiple(tpm, sbKeys, &creationParams)
   355  	if err != nil {
   356  		return err
   357  	}
   358  	if params.TPMPolicyAuthKeyFile != "" {
   359  		if err := osutil.AtomicWriteFile(params.TPMPolicyAuthKeyFile, authKey, 0600, 0); err != nil {
   360  			return fmt.Errorf("cannot write the policy auth key file: %v", err)
   361  		}
   362  	}
   363  
   364  	return nil
   365  }
   366  
   367  // ResealKeys updates the PCR protection policy for the sealed encryption keys
   368  // according to the specified parameters.
   369  func ResealKeys(params *ResealKeysParams) error {
   370  	numModels := len(params.ModelParams)
   371  	if numModels < 1 {
   372  		return fmt.Errorf("at least one set of model-specific parameters is required")
   373  	}
   374  
   375  	tpm, err := sbConnectToDefaultTPM()
   376  	if err != nil {
   377  		return fmt.Errorf("cannot connect to TPM: %v", err)
   378  	}
   379  	defer tpm.Close()
   380  	if !isTPMEnabled(tpm) {
   381  		return fmt.Errorf("TPM device is not enabled")
   382  	}
   383  
   384  	pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
   385  	if err != nil {
   386  		return err
   387  	}
   388  
   389  	authKey, err := ioutil.ReadFile(params.TPMPolicyAuthKeyFile)
   390  	if err != nil {
   391  		return fmt.Errorf("cannot read the policy auth key file: %v", err)
   392  	}
   393  
   394  	return sbUpdateKeyPCRProtectionPolicyMultiple(tpm, params.KeyFiles, authKey, pcrProfile)
   395  }
   396  
   397  func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb.PCRProtectionProfile, error) {
   398  	numModels := len(modelParams)
   399  	modelPCRProfiles := make([]*sb.PCRProtectionProfile, 0, numModels)
   400  
   401  	for _, mp := range modelParams {
   402  		modelProfile := sb.NewPCRProtectionProfile()
   403  
   404  		loadSequences, err := buildLoadSequences(mp.EFILoadChains)
   405  		if err != nil {
   406  			return nil, fmt.Errorf("cannot build EFI image load sequences: %v", err)
   407  		}
   408  
   409  		// Add EFI secure boot policy profile
   410  		policyParams := sb.EFISecureBootPolicyProfileParams{
   411  			PCRAlgorithm:  tpm2.HashAlgorithmSHA256,
   412  			LoadSequences: loadSequences,
   413  			// TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden
   414  			//            signature updates to blacklist signing keys (after rotating them).
   415  			//            This also requires integration of sbkeysync, and some work to
   416  			//            ensure that the PCR profile is updated before/after sbkeysync executes.
   417  		}
   418  
   419  		if err := sbAddEFISecureBootPolicyProfile(modelProfile, &policyParams); err != nil {
   420  			return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err)
   421  		}
   422  
   423  		// Add EFI boot manager profile
   424  		bootManagerParams := sb.EFIBootManagerProfileParams{
   425  			PCRAlgorithm:  tpm2.HashAlgorithmSHA256,
   426  			LoadSequences: loadSequences,
   427  		}
   428  		if err := sbAddEFIBootManagerProfile(modelProfile, &bootManagerParams); err != nil {
   429  			return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err)
   430  		}
   431  
   432  		// Add systemd EFI stub profile
   433  		if len(mp.KernelCmdlines) != 0 {
   434  			systemdStubParams := sb.SystemdEFIStubProfileParams{
   435  				PCRAlgorithm:   tpm2.HashAlgorithmSHA256,
   436  				PCRIndex:       initramfsPCR,
   437  				KernelCmdlines: mp.KernelCmdlines,
   438  			}
   439  			if err := sbAddSystemdEFIStubProfile(modelProfile, &systemdStubParams); err != nil {
   440  				return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err)
   441  			}
   442  		}
   443  
   444  		// Add snap model profile
   445  		if mp.Model != nil {
   446  			snapModelParams := sb.SnapModelProfileParams{
   447  				PCRAlgorithm: tpm2.HashAlgorithmSHA256,
   448  				PCRIndex:     initramfsPCR,
   449  				Models:       []sb.SnapModel{mp.Model},
   450  			}
   451  			if err := sbAddSnapModelProfile(modelProfile, &snapModelParams); err != nil {
   452  				return nil, fmt.Errorf("cannot add snap model profile: %v", err)
   453  			}
   454  		}
   455  
   456  		modelPCRProfiles = append(modelPCRProfiles, modelProfile)
   457  	}
   458  
   459  	var pcrProfile *sb.PCRProtectionProfile
   460  	if numModels > 1 {
   461  		pcrProfile = sb.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...)
   462  	} else {
   463  		pcrProfile = modelPCRProfiles[0]
   464  	}
   465  
   466  	logger.Debugf("PCR protection profile:\n%s", pcrProfile.String())
   467  
   468  	return pcrProfile, nil
   469  }
   470  
   471  func tpmProvision(tpm *sb.TPMConnection, lockoutAuthFile string) error {
   472  	// Create and save the lockout authorization file
   473  	lockoutAuth := make([]byte, 16)
   474  	// crypto rand is protected against short reads
   475  	_, err := rand.Read(lockoutAuth)
   476  	if err != nil {
   477  		return fmt.Errorf("cannot create lockout authorization: %v", err)
   478  	}
   479  	if err := osutil.AtomicWriteFile(lockoutAuthFile, lockoutAuth, 0600, 0); err != nil {
   480  		return fmt.Errorf("cannot write the lockout authorization file: %v", err)
   481  	}
   482  
   483  	// TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot
   484  	//            if the device has previously been provisioned, see
   485  	//            https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI
   486  	if err := provisionTPM(tpm, sb.ProvisionModeFull, lockoutAuth); err != nil {
   487  		logger.Noticef("TPM provisioning error: %v", err)
   488  		return fmt.Errorf("cannot provision TPM: %v", err)
   489  	}
   490  	return nil
   491  }
   492  
   493  func provisionTPMImpl(tpm *sb.TPMConnection, mode sb.ProvisionMode, lockoutAuth []byte) error {
   494  	return tpm.EnsureProvisioned(mode, lockoutAuth)
   495  }
   496  
   497  // buildLoadSequences builds EFI load image event trees from this package LoadChains
   498  func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb.EFIImageLoadEvent, err error) {
   499  	// this will build load event trees for the current
   500  	// device configuration, e.g. something like:
   501  	//
   502  	// shim -> recovery grub -> recovery kernel 1
   503  	//                      |-> recovery kernel 2
   504  	//                      |-> recovery kernel ...
   505  	//                      |-> normal grub -> run kernel good
   506  	//                                     |-> run kernel try
   507  
   508  	for _, chain := range chains {
   509  		// root of load events has source Firmware
   510  		loadseq, err := chain.loadEvent(sb.Firmware)
   511  		if err != nil {
   512  			return nil, err
   513  		}
   514  		loadseqs = append(loadseqs, loadseq)
   515  	}
   516  	return loadseqs, nil
   517  }
   518  
   519  // loadEvent builds the corresponding load event and its tree
   520  func (lc *LoadChain) loadEvent(source sb.EFIImageLoadEventSource) (*sb.EFIImageLoadEvent, error) {
   521  	var next []*sb.EFIImageLoadEvent
   522  	for _, nextChain := range lc.Next {
   523  		// everything that is not the root has source shim
   524  		ev, err := nextChain.loadEvent(sb.Shim)
   525  		if err != nil {
   526  			return nil, err
   527  		}
   528  		next = append(next, ev)
   529  	}
   530  	image, err := efiImageFromBootFile(lc.BootFile)
   531  	if err != nil {
   532  		return nil, err
   533  	}
   534  	return &sb.EFIImageLoadEvent{
   535  		Source: source,
   536  		Image:  image,
   537  		Next:   next,
   538  	}, nil
   539  }
   540  
   541  func efiImageFromBootFile(b *bootloader.BootFile) (sb.EFIImage, error) {
   542  	if b.Snap == "" {
   543  		if !osutil.FileExists(b.Path) {
   544  			return nil, fmt.Errorf("file %s does not exist", b.Path)
   545  		}
   546  		return sb.FileEFIImage(b.Path), nil
   547  	}
   548  
   549  	snapf, err := snapfile.Open(b.Snap)
   550  	if err != nil {
   551  		return nil, err
   552  	}
   553  	return sb.SnapFileEFIImage{
   554  		Container: snapf,
   555  		FileName:  b.Path,
   556  	}, nil
   557  }