-
Notifications
You must be signed in to change notification settings - Fork 601
/
Copy pathsecboot_tpm.go
716 lines (629 loc) · 23.5 KB
/
secboot_tpm.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
// -*- Mode: Go; indent-tabs-mode: t -*-
//go:build !nosecboot
/*
* Copyright (C) 2021 Canonical Ltd
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 3 as
* published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/
package secboot
import (
"crypto/rand"
"errors"
"fmt"
"os"
"path/filepath"
"strings"
"github.com/canonical/go-tpm2"
"github.com/canonical/go-tpm2/mu"
sb "github.com/snapcore/secboot"
sb_efi "github.com/snapcore/secboot/efi"
sb_tpm2 "github.com/snapcore/secboot/tpm2"
"golang.org/x/xerrors"
"github.com/snapcore/snapd/asserts"
"github.com/snapcore/snapd/bootloader"
"github.com/snapcore/snapd/bootloader/efi"
"github.com/snapcore/snapd/logger"
"github.com/snapcore/snapd/osutil"
"github.com/snapcore/snapd/randutil"
"github.com/snapcore/snapd/snap/snapfile"
)
const (
keyringPrefix = "ubuntu-fde"
)
var (
sbConnectToDefaultTPM = sb_tpm2.ConnectToDefaultTPM
sbMeasureSnapSystemEpochToTPM = sb_tpm2.MeasureSnapSystemEpochToTPM
sbMeasureSnapModelToTPM = sb_tpm2.MeasureSnapModelToTPM
sbBlockPCRProtectionPolicies = sb_tpm2.BlockPCRProtectionPolicies
sbefiAddSecureBootPolicyProfile = sb_efi.AddSecureBootPolicyProfile
sbefiAddBootManagerProfile = sb_efi.AddBootManagerProfile
sbefiAddSystemdStubProfile = sb_efi.AddSystemdStubProfile
sbAddSnapModelProfile = sb_tpm2.AddSnapModelProfile
sbSealKeyToTPMMultiple = sb_tpm2.SealKeyToTPMMultiple
sbUpdateKeyPCRProtectionPolicyMultiple = sb_tpm2.UpdateKeyPCRProtectionPolicyMultiple
sbSealedKeyObjectRevokeOldPCRProtectionPolicies = (*sb_tpm2.SealedKeyObject).RevokeOldPCRProtectionPolicies
sbNewKeyDataFromSealedKeyObjectFile = sb_tpm2.NewKeyDataFromSealedKeyObjectFile
sbReadSealedKeyObjectFromFile = sb_tpm2.ReadSealedKeyObjectFromFile
randutilRandomKernelUUID = randutil.RandomKernelUUID
isTPMEnabled = (*sb_tpm2.Connection).IsEnabled
lockoutAuthSet = (*sb_tpm2.Connection).LockoutAuthSet
sbTPMEnsureProvisioned = (*sb_tpm2.Connection).EnsureProvisioned
sbTPMEnsureProvisionedWithCustomSRK = (*sb_tpm2.Connection).EnsureProvisionedWithCustomSRK
tpmReleaseResources = tpmReleaseResourcesImpl
sbTPMDictionaryAttackLockReset = (*sb_tpm2.Connection).DictionaryAttackLockReset
// check whether the interfaces match
_ (sb.SnapModel) = ModelForSealing(nil)
)
func CheckTPMKeySealingSupported(mode TPMProvisionMode) error {
logger.Noticef("checking if secure boot is enabled...")
if err := checkSecureBootEnabled(); err != nil {
logger.Noticef("secure boot not enabled: %v", err)
return err
}
logger.Noticef("secure boot is enabled")
logger.Noticef("checking if TPM device is available...")
tpm, err := sbConnectToDefaultTPM()
if err != nil {
err = fmt.Errorf("cannot connect to TPM device: %v", err)
logger.Noticef("%v", err)
return err
}
defer tpm.Close()
if !isTPMEnabled(tpm) {
logger.Noticef("TPM device detected but not enabled")
return fmt.Errorf("TPM device is not enabled")
}
if mode == TPMProvisionFull {
if lockoutAuthSet(tpm) {
logger.Noticef("TPM in lockout mode")
return sb_tpm2.ErrTPMLockout
}
}
logger.Noticef("TPM device detected and enabled")
return nil
}
func checkSecureBootEnabled() error {
// 8be4df61-93ca-11d2-aa0d-00e098032b8c is the EFI Global Variable vendor GUID
b, _, err := efi.ReadVarBytes("SecureBoot-8be4df61-93ca-11d2-aa0d-00e098032b8c")
if err != nil {
if err == efi.ErrNoEFISystem {
return err
}
return fmt.Errorf("cannot read secure boot variable: %v", err)
}
if len(b) < 1 {
return errors.New("secure boot variable does not exist")
}
if b[0] != 1 {
return errors.New("secure boot is disabled")
}
return nil
}
// initramfsPCR is the TPM PCR that we reserve for the EFI image and use
// for measurement from the initramfs.
const initramfsPCR = 12
func insecureConnectToTPM() (*sb_tpm2.Connection, error) {
return sbConnectToDefaultTPM()
}
func measureWhenPossible(whatHow func(tpm *sb_tpm2.Connection) error) error {
// the model is ready, we're good to try measuring it now
tpm, err := insecureConnectToTPM()
if err != nil {
if xerrors.Is(err, sb_tpm2.ErrNoTPM2Device) {
return nil
}
return fmt.Errorf("cannot open TPM connection: %v", err)
}
defer tpm.Close()
if !isTPMEnabled(tpm) {
return nil
}
return whatHow(tpm)
}
// MeasureSnapSystemEpochWhenPossible measures the snap system epoch only if the
// TPM device is available. If there's no TPM device success is returned.
func MeasureSnapSystemEpochWhenPossible() error {
measure := func(tpm *sb_tpm2.Connection) error {
return sbMeasureSnapSystemEpochToTPM(tpm, initramfsPCR)
}
if err := measureWhenPossible(measure); err != nil {
return fmt.Errorf("cannot measure snap system epoch: %v", err)
}
return nil
}
// MeasureSnapModelWhenPossible measures the snap model only if the TPM device is
// available. If there's no TPM device success is returned.
func MeasureSnapModelWhenPossible(findModel func() (*asserts.Model, error)) error {
measure := func(tpm *sb_tpm2.Connection) error {
model, err := findModel()
if err != nil {
return err
}
return sbMeasureSnapModelToTPM(tpm, initramfsPCR, model)
}
if err := measureWhenPossible(measure); err != nil {
return fmt.Errorf("cannot measure snap model: %v", err)
}
return nil
}
func lockTPMSealedKeys() error {
tpm, tpmErr := sbConnectToDefaultTPM()
if tpmErr != nil {
if xerrors.Is(tpmErr, sb_tpm2.ErrNoTPM2Device) {
logger.Noticef("cannot open TPM connection: %v", tpmErr)
return nil
}
return fmt.Errorf("cannot lock TPM: %v", tpmErr)
}
defer tpm.Close()
// Lock access to the sealed keys. This should be called whenever there
// is a TPM device detected, regardless of whether secure boot is enabled
// or there is an encrypted volume to unlock. Note that snap-bootstrap can
// be called several times during initialization, and if there are multiple
// volumes to unlock we should lock access to the sealed keys only after
// the last encrypted volume is unlocked, in which case lockKeysOnFinish
// should be set to true.
//
// We should only touch the PCR that we've currently reserved for the kernel
// EFI image. Touching others will break the ability to perform any kind of
// attestation using the TPM because it will make the log inconsistent.
return sbBlockPCRProtectionPolicies(tpm, []int{initramfsPCR})
}
func unlockVolumeUsingSealedKeyTPM(name, sealedEncryptionKeyFile, sourceDevice, targetDevice, mapperName string, opts *UnlockVolumeUsingSealedKeyOptions) (UnlockResult, error) {
// TODO:UC20: use sb.SecureConnectToDefaultTPM() if we decide there's benefit in doing that or
// we have a hard requirement for a valid EK cert chain for every boot (ie, panic
// if there isn't one). But we can't do that as long as we need to download
// intermediate certs from the manufacturer.
res := UnlockResult{IsEncrypted: true, PartDevice: sourceDevice}
tpmDeviceAvailable := false
// Obtain a TPM connection.
if tpm, tpmErr := sbConnectToDefaultTPM(); tpmErr != nil {
if !xerrors.Is(tpmErr, sb_tpm2.ErrNoTPM2Device) {
return res, fmt.Errorf("cannot unlock encrypted device %q: %v", name, tpmErr)
}
logger.Noticef("cannot open TPM connection: %v", tpmErr)
} else {
// Also check if the TPM device is enabled. The platform firmware may disable the storage
// and endorsement hierarchies, but the device will remain visible to the operating system.
tpmDeviceAvailable = isTPMEnabled(tpm)
// later during ActivateVolumeWithKeyData secboot will
// open the TPM again, close it as it can't be opened
// multiple times and also we are done using it here
tpm.Close()
}
// if we don't have a tpm, and we allow using a recovery key, do that
// directly
if !tpmDeviceAvailable && opts.AllowRecoveryKey {
if err := UnlockEncryptedVolumeWithRecoveryKey(mapperName, sourceDevice); err != nil {
return res, err
}
res.FsDevice = targetDevice
res.UnlockMethod = UnlockedWithRecoveryKey
return res, nil
}
// otherwise we have a tpm and we should use the sealed key first, but
// this method will fallback to using the recovery key if enabled
method, err := unlockEncryptedPartitionWithSealedKey(mapperName, sourceDevice, sealedEncryptionKeyFile, opts.AllowRecoveryKey)
res.UnlockMethod = method
if err == nil {
res.FsDevice = targetDevice
}
return res, err
}
func activateVolOpts(allowRecoveryKey bool) *sb.ActivateVolumeOptions {
options := sb.ActivateVolumeOptions{
PassphraseTries: 1,
// disable recovery key by default
RecoveryKeyTries: 0,
KeyringPrefix: keyringPrefix,
}
if allowRecoveryKey {
// enable recovery key only when explicitly allowed
options.RecoveryKeyTries = 3
}
return &options
}
// unlockEncryptedPartitionWithSealedKey unseals the keyfile and opens an encrypted
// device. If activation with the sealed key fails, this function will attempt to
// activate it with the fallback recovery key instead.
func unlockEncryptedPartitionWithSealedKey(mapperName, sourceDevice, keyfile string, allowRecovery bool) (UnlockMethod, error) {
keyData, err := sbNewKeyDataFromSealedKeyObjectFile(keyfile)
if err != nil {
return NotUnlocked, fmt.Errorf("cannot read key data: %v", err)
}
options := activateVolOpts(allowRecovery)
// ignoring model checker as it doesn't work with tpm "legacy" platform key data
_, err = sbActivateVolumeWithKeyData(mapperName, sourceDevice, keyData, options)
if err == sb.ErrRecoveryKeyUsed {
logger.Noticef("successfully activated encrypted device %q using a fallback activation method", sourceDevice)
return UnlockedWithRecoveryKey, nil
}
if err != nil {
return NotUnlocked, fmt.Errorf("cannot activate encrypted device %q: %v", sourceDevice, err)
}
logger.Noticef("successfully activated encrypted device %q with TPM", sourceDevice)
return UnlockedWithSealedKey, nil
}
// ProvisionTPM provisions the default TPM and saves the lockout authorization
// key to the specified file.
func ProvisionTPM(mode TPMProvisionMode, lockoutAuthFile string) error {
tpm, err := sbConnectToDefaultTPM()
if err != nil {
return fmt.Errorf("cannot connect to TPM: %v", err)
}
defer tpm.Close()
if !isTPMEnabled(tpm) {
return fmt.Errorf("TPM device is not enabled")
}
if err := tpmProvision(tpm, mode, lockoutAuthFile); err != nil {
return err
}
return nil
}
// ProvisionForCVM provisions the default TPM using a custom SRK
// template that is created by the encrypt tool prior to first boot of
// Azure CVM instances. It takes UbuntuSeedDir (ESP) and expects
// "tpm2-srk.tmpl" there which is deleted after successful provision.
//
// Key differences with ProvisionTPM()
// - lack of TPM or if TPM is disabled is ignored.
// - it is fatal if TPM Provisioning requires a Lockout file
// - Custom SRK file is required in InitramfsUbuntuSeedDir
func ProvisionForCVM(initramfsUbuntuSeedDir string) error {
tpm, err := insecureConnectToTPM()
if err != nil {
if xerrors.Is(err, sb_tpm2.ErrNoTPM2Device) {
return nil
}
return fmt.Errorf("cannot open TPM connection: %v", err)
}
defer tpm.Close()
if !isTPMEnabled(tpm) {
return nil
}
srkTmplPath := filepath.Join(initramfsUbuntuSeedDir, "tpm2-srk.tmpl")
f, err := os.Open(srkTmplPath)
if err != nil {
if os.IsNotExist(err) {
return nil
}
return fmt.Errorf("cannot open SRK template file: %v", err)
}
defer f.Close()
var srkTmpl *tpm2.Public
if _, err := mu.UnmarshalFromReader(f, mu.Sized(&srkTmpl)); err != nil {
return fmt.Errorf("cannot read SRK template: %v", err)
}
err = sbTPMEnsureProvisionedWithCustomSRK(tpm, sb_tpm2.ProvisionModeWithoutLockout, nil, srkTmpl)
if err != nil && err != sb_tpm2.ErrTPMProvisioningRequiresLockout {
return fmt.Errorf("cannot prepare TPM: %v", err)
}
if err := os.Remove(srkTmplPath); err != nil {
return fmt.Errorf("cannot remove SRK template file: %v", err)
}
return nil
}
// SealKeys seals the encryption keys according to the specified parameters. The
// TPM must have already been provisioned. If sealed key already exists at the
// PCR handle, SealKeys will fail and return an error.
func SealKeys(keys []SealKeyRequest, params *SealKeysParams) error {
numModels := len(params.ModelParams)
if numModels < 1 {
return fmt.Errorf("at least one set of model-specific parameters is required")
}
tpm, err := sbConnectToDefaultTPM()
if err != nil {
return fmt.Errorf("cannot connect to TPM: %v", err)
}
defer tpm.Close()
if !isTPMEnabled(tpm) {
return fmt.Errorf("TPM device is not enabled")
}
pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
if err != nil {
return err
}
pcrHandle := params.PCRPolicyCounterHandle
logger.Noticef("sealing with PCR handle %#x", pcrHandle)
// Seal the provided keys to the TPM
creationParams := sb_tpm2.KeyCreationParams{
PCRProfile: pcrProfile,
PCRPolicyCounterHandle: tpm2.Handle(pcrHandle),
AuthKey: params.TPMPolicyAuthKey,
}
sbKeys := make([]*sb_tpm2.SealKeyRequest, 0, len(keys))
for i := range keys {
sbKeys = append(sbKeys, &sb_tpm2.SealKeyRequest{
Key: keys[i].Key,
Path: keys[i].KeyFile,
})
}
authKey, err := sbSealKeyToTPMMultiple(tpm, sbKeys, &creationParams)
if err != nil {
logger.Debugf("seal key error: %v", err)
return err
}
if params.TPMPolicyAuthKeyFile != "" {
if err := osutil.AtomicWriteFile(params.TPMPolicyAuthKeyFile, authKey, 0600, 0); err != nil {
return fmt.Errorf("cannot write the policy auth key file: %v", err)
}
}
return nil
}
// ResealKeys updates the PCR protection policy for the sealed encryption keys
// according to the specified parameters.
func ResealKeys(params *ResealKeysParams) error {
numModels := len(params.ModelParams)
if numModels < 1 {
return fmt.Errorf("at least one set of model-specific parameters is required")
}
numSealedKeyObjects := len(params.KeyFiles)
if numSealedKeyObjects < 1 {
return fmt.Errorf("at least one key file is required")
}
tpm, err := sbConnectToDefaultTPM()
if err != nil {
return fmt.Errorf("cannot connect to TPM: %v", err)
}
defer tpm.Close()
if !isTPMEnabled(tpm) {
return fmt.Errorf("TPM device is not enabled")
}
pcrProfile, err := buildPCRProtectionProfile(params.ModelParams)
if err != nil {
return err
}
authKey, err := os.ReadFile(params.TPMPolicyAuthKeyFile)
if err != nil {
return fmt.Errorf("cannot read the policy auth key file: %v", err)
}
sealedKeyObjects := make([]*sb_tpm2.SealedKeyObject, 0, numSealedKeyObjects)
for _, keyfile := range params.KeyFiles {
sealedKeyObject, err := sbReadSealedKeyObjectFromFile(keyfile)
if err != nil {
return err
}
sealedKeyObjects = append(sealedKeyObjects, sealedKeyObject)
}
if err := sbUpdateKeyPCRProtectionPolicyMultiple(tpm, sealedKeyObjects, authKey, pcrProfile); err != nil {
return err
}
// write key files
for i, sko := range sealedKeyObjects {
w := sb_tpm2.NewFileSealedKeyObjectWriter(params.KeyFiles[i])
if err := sko.WriteAtomic(w); err != nil {
return fmt.Errorf("cannot write key data file: %v", err)
}
}
// revoke old policies via the primary key object
return sbSealedKeyObjectRevokeOldPCRProtectionPolicies(sealedKeyObjects[0], tpm, authKey)
}
func buildPCRProtectionProfile(modelParams []*SealKeyModelParams) (*sb_tpm2.PCRProtectionProfile, error) {
numModels := len(modelParams)
modelPCRProfiles := make([]*sb_tpm2.PCRProtectionProfile, 0, numModels)
for _, mp := range modelParams {
modelProfile := sb_tpm2.NewPCRProtectionProfile()
loadSequences, err := buildLoadSequences(mp.EFILoadChains)
if err != nil {
return nil, fmt.Errorf("cannot build EFI image load sequences: %v", err)
}
// Add EFI secure boot policy profile
policyParams := sb_efi.SecureBootPolicyProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
LoadSequences: loadSequences,
// TODO:UC20: set SignatureDbUpdateKeystore to support applying forbidden
// signature updates to exclude signing keys (after rotating them).
// This also requires integration of sbkeysync, and some work to
// ensure that the PCR profile is updated before/after sbkeysync executes.
}
if err := sbefiAddSecureBootPolicyProfile(modelProfile, &policyParams); err != nil {
return nil, fmt.Errorf("cannot add EFI secure boot policy profile: %v", err)
}
// Add EFI boot manager profile
bootManagerParams := sb_efi.BootManagerProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
LoadSequences: loadSequences,
}
if err := sbefiAddBootManagerProfile(modelProfile, &bootManagerParams); err != nil {
return nil, fmt.Errorf("cannot add EFI boot manager profile: %v", err)
}
// Add systemd EFI stub profile
if len(mp.KernelCmdlines) != 0 {
systemdStubParams := sb_efi.SystemdStubProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
PCRIndex: initramfsPCR,
KernelCmdlines: mp.KernelCmdlines,
}
if err := sbefiAddSystemdStubProfile(modelProfile, &systemdStubParams); err != nil {
return nil, fmt.Errorf("cannot add systemd EFI stub profile: %v", err)
}
}
// Add snap model profile
if mp.Model != nil {
snapModelParams := sb_tpm2.SnapModelProfileParams{
PCRAlgorithm: tpm2.HashAlgorithmSHA256,
PCRIndex: initramfsPCR,
Models: []sb.SnapModel{mp.Model},
}
if err := sbAddSnapModelProfile(modelProfile, &snapModelParams); err != nil {
return nil, fmt.Errorf("cannot add snap model profile: %v", err)
}
}
modelPCRProfiles = append(modelPCRProfiles, modelProfile)
}
var pcrProfile *sb_tpm2.PCRProtectionProfile
if numModels > 1 {
pcrProfile = sb_tpm2.NewPCRProtectionProfile().AddProfileOR(modelPCRProfiles...)
} else {
pcrProfile = modelPCRProfiles[0]
}
logger.Debugf("PCR protection profile:\n%s", pcrProfile.String())
return pcrProfile, nil
}
func tpmProvision(tpm *sb_tpm2.Connection, mode TPMProvisionMode, lockoutAuthFile string) error {
var currentLockoutAuth []byte
if mode == TPMPartialReprovision {
logger.Debugf("using existing lockout authorization")
d, err := os.ReadFile(lockoutAuthFile)
if err != nil {
return fmt.Errorf("cannot read existing lockout auth: %v", err)
}
currentLockoutAuth = d
}
// Create and save the lockout authorization file
lockoutAuth := make([]byte, 16)
// crypto rand is protected against short reads
_, err := rand.Read(lockoutAuth)
if err != nil {
return fmt.Errorf("cannot create lockout authorization: %v", err)
}
if err := osutil.AtomicWriteFile(lockoutAuthFile, lockoutAuth, 0600, 0); err != nil {
return fmt.Errorf("cannot write the lockout authorization file: %v", err)
}
// TODO:UC20: ideally we should ask the firmware to clear the TPM and then reboot
// if the device has previously been provisioned, see
// https://godoc.org/github.com/snapcore/secboot#RequestTPMClearUsingPPI
if currentLockoutAuth != nil {
// use the current lockout authorization data to authorize
// provisioning
tpm.LockoutHandleContext().SetAuthValue(currentLockoutAuth)
}
if err := sbTPMEnsureProvisioned(tpm, sb_tpm2.ProvisionModeFull, lockoutAuth); err != nil {
logger.Noticef("TPM provisioning error: %v", err)
return fmt.Errorf("cannot provision TPM: %v", err)
}
return nil
}
// buildLoadSequences builds EFI load image event trees from this package LoadChains
func buildLoadSequences(chains []*LoadChain) (loadseqs []*sb_efi.ImageLoadEvent, err error) {
// this will build load event trees for the current
// device configuration, e.g. something like:
//
// shim -> recovery grub -> recovery kernel 1
// |-> recovery kernel 2
// |-> recovery kernel ...
// |-> normal grub -> run kernel good
// |-> run kernel try
for _, chain := range chains {
// root of load events has source Firmware
loadseq, err := chain.loadEvent(sb_efi.Firmware)
if err != nil {
return nil, err
}
loadseqs = append(loadseqs, loadseq)
}
return loadseqs, nil
}
// loadEvent builds the corresponding load event and its tree
func (lc *LoadChain) loadEvent(source sb_efi.ImageLoadEventSource) (*sb_efi.ImageLoadEvent, error) {
var next []*sb_efi.ImageLoadEvent
for _, nextChain := range lc.Next {
// everything that is not the root has source shim
ev, err := nextChain.loadEvent(sb_efi.Shim)
if err != nil {
return nil, err
}
next = append(next, ev)
}
image, err := efiImageFromBootFile(lc.BootFile)
if err != nil {
return nil, err
}
return &sb_efi.ImageLoadEvent{
Source: source,
Image: image,
Next: next,
}, nil
}
func efiImageFromBootFile(b *bootloader.BootFile) (sb_efi.Image, error) {
if b.Snap == "" {
if !osutil.FileExists(b.Path) {
return nil, fmt.Errorf("file %s does not exist", b.Path)
}
return sb_efi.FileImage(b.Path), nil
}
snapf, err := snapfile.Open(b.Snap)
if err != nil {
return nil, err
}
return sb_efi.SnapFileImage{
Container: snapf,
FileName: b.Path,
}, nil
}
// PCRHandleOfSealedKey retunrs the PCR handle which was used when sealing a
// given key object.
func PCRHandleOfSealedKey(p string) (uint32, error) {
r, err := sb_tpm2.NewFileSealedKeyObjectReader(p)
if err != nil {
return 0, fmt.Errorf("cannot open key file: %v", err)
}
sko, err := sb_tpm2.ReadSealedKeyObject(r)
if err != nil {
return 0, fmt.Errorf("cannot read sealed key file: %v", err)
}
handle := uint32(sko.PCRPolicyCounterHandle())
return handle, nil
}
func tpmReleaseResourcesImpl(tpm *sb_tpm2.Connection, handle tpm2.Handle) error {
rc, err := tpm.CreateResourceContextFromTPM(handle)
if err != nil {
if _, ok := err.(tpm2.ResourceUnavailableError); ok {
// there's nothing to release, the handle isn't used
return nil
}
return fmt.Errorf("cannot create resource context: %v", err)
}
if err := tpm.NVUndefineSpace(tpm.OwnerHandleContext(), rc, tpm.HmacSession()); err != nil {
return fmt.Errorf("cannot undefine space: %v", err)
}
return nil
}
// ReleasePCRResourceHandles releases any TPM resources associated with given
// PCR handles.
func ReleasePCRResourceHandles(handles ...uint32) error {
tpm, err := sbConnectToDefaultTPM()
if err != nil {
err = fmt.Errorf("cannot connect to TPM device: %v", err)
return err
}
defer tpm.Close()
var errs []string
for _, handle := range handles {
logger.Debugf("releasing PCR handle %#x", handle)
if err := tpmReleaseResources(tpm, tpm2.Handle(handle)); err != nil {
errs = append(errs, fmt.Sprintf("handle %#x: %v", handle, err))
}
}
if errCnt := len(errs); errCnt != 0 {
return fmt.Errorf("cannot release TPM resources for %v handles:\n%v", errCnt, strings.Join(errs, "\n"))
}
return nil
}
func resetLockoutCounter(lockoutAuthFile string) error {
tpm, err := sbConnectToDefaultTPM()
if err != nil {
return fmt.Errorf("cannot connect to TPM: %v", err)
}
defer tpm.Close()
lockoutAuth, err := os.ReadFile(lockoutAuthFile)
if err != nil {
return fmt.Errorf("cannot read existing lockout auth: %v", err)
}
tpm.LockoutHandleContext().SetAuthValue(lockoutAuth)
if err := sbTPMDictionaryAttackLockReset(tpm, tpm.LockoutHandleContext(), tpm.HmacSession()); err != nil {
return err
}
return nil
}