Skip to content

Commit dc7ca1e

Browse files
committed
Add dynamic PCR policy support for disk key sealing
Implement configurable PCR selection for TPM disk key sealing/unsealing operations. This allows the controller to specify which PCRs should be used when sealing the vault key, enabling more flexible TPM measurement policies. changes: - Add GetDiskKeySealingPCRs() to read PCR policy from persistent storage - Add SaveDiskKeySealingPCRs() with validation for PCR policy indexes - Rename DiskKeySealingPCRs to DefaultDiskKeySealingPCRs for clarity - Update vaultmgr to handle controller-provided PCR policies - Persist PCR policy - Update all callers to use dynamic PCR selection Signed-off-by: Shahriyar Jalayeri <[email protected]>
1 parent e81177b commit dc7ca1e

File tree

4 files changed

+481
-27
lines changed

4 files changed

+481
-27
lines changed

pkg/pillar/cmd/vaultmgr/vaultmgr.go

Lines changed: 33 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,14 @@ func handleVaultKeyFromControllerImpl(ctxArg interface{}, key string,
421421
return
422422
}
423423

424+
// better safe than sorry, even if golang GC should take care of it.
425+
defer func() {
426+
// zero out decrypted key
427+
for i := range decryptedKey {
428+
decryptedKey[i] = 0
429+
}
430+
}()
431+
424432
hash := sha256.New()
425433
hash.Write(decryptedKey)
426434
digest256 := hash.Sum(nil)
@@ -430,18 +438,36 @@ func handleVaultKeyFromControllerImpl(ctxArg interface{}, key string,
430438
}
431439
log.Functionf("Computed and provided SHA are matching")
432440

433-
if ctx.defaultVaultUnlocked {
434-
return
441+
// TODO : update this after eve-api change is merged
442+
pcrSelection := etpm.DefaultDiskKeySealingPCRs
443+
mockSealingPCRs := etpm.SealingPcrPolicyIndexes{
444+
Pcrs: etpm.DefaultDiskKeySealingPCRs.PCRs,
445+
Id: 0,
435446
}
436-
// Try unlocking the vault now, in case it is not yet unlocked
437-
log.Noticef("Vault is still locked, trying to unlock")
438-
err = etpm.SealDiskKey(log, decryptedKey, etpm.DiskKeySealingPCRs)
447+
pcrSelection, policyChanged, err := etpm.SaveDiskKeySealingPCRs(mockSealingPCRs, etpm.PcrPolicyIndexesFile, etpm.PcrPolicyIndexesHashFile)
439448
if err != nil {
440-
log.Errorf("Failed to Seal key in TPM %v", err)
449+
log.Errorf("Failed to save controller provided PCR Policy sealing PCRs: %v", err)
441450
return
442451
}
443-
log.Noticef("Sealed key in TPM, unlocking %s", types.DefaultVaultName)
444452

453+
// if policy has changed, or we have not yet unlocked the vault,
454+
// re-seal the disk key we got from controller.
455+
if policyChanged || !ctx.defaultVaultUnlocked {
456+
log.Noticef("Sealing disk key, Vault Unlocked: %v, Policy Changed: %v", ctx.defaultVaultUnlocked, policyChanged)
457+
err = etpm.SealDiskKey(log, decryptedKey, pcrSelection)
458+
if err != nil {
459+
log.Errorf("Failed to re-seal disk key in TPM %v", err)
460+
return
461+
}
462+
log.Noticef("Re-sealed disk key in TPM.")
463+
}
464+
465+
// If we have already unlocked the vault, nothing to do
466+
if ctx.defaultVaultUnlocked {
467+
return
468+
}
469+
470+
log.Noticef("Sealed key in TPM, unlocking %s", types.DefaultVaultName)
445471
err = handler.UnlockDefaultVault()
446472
if err != nil {
447473
log.Errorf("Failed to unlock zfs vault after receiving Controller key, %v",

pkg/pillar/evetpm/tpm.go

Lines changed: 126 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"crypto/x509"
1111
"encoding/asn1"
1212
"encoding/gob"
13+
"encoding/json"
1314
"encoding/pem"
1415
"fmt"
1516
"io"
@@ -72,8 +73,17 @@ const (
7273
//EmptyPassword is an empty string
7374
EmptyPassword = ""
7475
vaultKeyLength = 32 //Bytes
76+
77+
// TODO : rename and move this to locations
78+
PcrPolicyIndexesFile = types.PersistStatusDir + "/sealingpcrs.json"
79+
PcrPolicyIndexesHashFile = types.PersistStatusDir + "/sealingpcrs.hash"
7580
)
7681

82+
type SealingPcrPolicyIndexes struct {
83+
Pcrs []int
84+
Id int
85+
}
86+
7787
// PCRBank256Status stores info about support for
7888
// SHA256 PCR bank on this device
7989
type PCRBank256Status uint32
@@ -98,7 +108,7 @@ var (
98108
pcrBank256Status = PCRBank256StatusUnknown
99109

100110
//DiskKeySealingPCRs represents PCRs that we use for sealing
101-
DiskKeySealingPCRs = tpm2.PCRSelection{Hash: tpm2.AlgSHA256, PCRs: []int{0, 1, 2, 3, 4, 6, 7, 8, 9, 13, 14}}
111+
DefaultDiskKeySealingPCRs = tpm2.PCRSelection{Hash: tpm2.AlgSHA256, PCRs: []int{0, 1, 2, 3, 4, 6, 7, 8, 9, 13, 14}}
102112

103113
// TpmDevicePath is the TPM device file path, it is not a constant due to
104114
// test usage.
@@ -259,6 +269,109 @@ var (
259269
}
260270
)
261271

272+
// GetDiskKeySealingPCRs returns the PCR selection to use for sealing the disk key.
273+
// It reads from the saved sealing PCRs file if it exists, otherwise returns the default.
274+
func GetDiskKeySealingPCRs(log *base.LogObject, path string) tpm2.PCRSelection {
275+
data, err := os.ReadFile(path)
276+
if err != nil {
277+
if log != nil {
278+
log.Warnf("failed to read sealing PCRs file: %v, using default PCR list: %v", err, DefaultDiskKeySealingPCRs.PCRs)
279+
}
280+
return DefaultDiskKeySealingPCRs
281+
}
282+
283+
var sp SealingPcrPolicyIndexes
284+
if err := json.Unmarshal(data, &sp); err != nil {
285+
if log != nil {
286+
log.Warnf("failed to unmarshal sealing PCRs: %v, using default PCR list: %v", err, DefaultDiskKeySealingPCRs.PCRs)
287+
}
288+
return DefaultDiskKeySealingPCRs
289+
}
290+
291+
pcrs := make([]int, len(sp.Pcrs))
292+
for i, pcr := range sp.Pcrs {
293+
pcrs[i] = int(pcr)
294+
}
295+
296+
return tpm2.PCRSelection{Hash: tpm2.AlgSHA256, PCRs: pcrs}
297+
}
298+
299+
// SaveDiskKeySealingPCRs saves the PCR policy indexes to a file.
300+
func SaveDiskKeySealingPCRs(sp SealingPcrPolicyIndexes, policyPath, policyHashPath string) (tpm2.PCRSelection, bool, error) {
301+
// Check if the list is empty
302+
if len(sp.Pcrs) == 0 {
303+
return tpm2.PCRSelection{}, false, fmt.Errorf("PCR list cannot be empty")
304+
}
305+
306+
// Check for maximum count (reasonable limit for PCR selection)
307+
if len(sp.Pcrs) > 16 {
308+
return tpm2.PCRSelection{}, false, fmt.Errorf("too many PCRs in policy: maximum 16 allowed, got %d", len(sp.Pcrs))
309+
}
310+
311+
hasPCR0 := false
312+
seenPCRs := make(map[int]bool)
313+
for _, pcr := range sp.Pcrs {
314+
// Check for duplicate PCR indexes
315+
if seenPCRs[pcr] {
316+
return tpm2.PCRSelection{}, false, fmt.Errorf("duplicate PCR index %d in policy", pcr)
317+
}
318+
seenPCRs[pcr] = true
319+
// PCR indexes must be between 0 and 15 inclusive, otherwise reject the policy.
320+
if pcr < 0 || pcr > 15 {
321+
return tpm2.PCRSelection{}, false, fmt.Errorf("invalid PCR index %d in policy: must be between 0 and 15", pcr)
322+
}
323+
// PCR 5 is used for GPT partition table and boot manager configuration,
324+
// which can be volatile and unsuitable for sealing in many scenarios.
325+
if pcr == 5 {
326+
return tpm2.PCRSelection{}, false, fmt.Errorf("invalid policy, PCR 5 is volatile (GPT/boot manager) and should not be included")
327+
}
328+
// PCR 16 is resetable debug PCR, reject it.
329+
if pcr == 16 {
330+
return tpm2.PCRSelection{}, false, fmt.Errorf("invalid policy, PCR 16 is a debug PCR and should not be included")
331+
}
332+
// PCR 17-22 are used for DTRM, PCR 23 is resetable and also used for DTRM, reject them.
333+
if pcr >= 17 && pcr <= 23 {
334+
return tpm2.PCRSelection{}, false, fmt.Errorf("invalid policy, PCR %d is used for DTRM and should not be included", pcr)
335+
}
336+
// PCR 0 is static root of trust for measurement (SRTM), must be included.
337+
if pcr == 0 {
338+
hasPCR0 = true
339+
}
340+
}
341+
if !hasPCR0 {
342+
return tpm2.PCRSelection{}, false, fmt.Errorf("PCR 0 must be in the list")
343+
}
344+
345+
data, err := json.Marshal(sp)
346+
if err != nil {
347+
return tpm2.PCRSelection{}, false, fmt.Errorf("failed to marshal sealing PCR policy indexes: %w", err)
348+
}
349+
350+
// Calculate hash of the policy
351+
hash := crypto.SHA256.New()
352+
hash.Write(data)
353+
policyHash := hash.Sum(nil)
354+
355+
// Check if policy has changed
356+
existingHash, err := os.ReadFile(policyHashPath)
357+
if err == nil && bytes.Equal(existingHash, policyHash) {
358+
return tpm2.PCRSelection{Hash: tpm2.AlgSHA256, PCRs: sp.Pcrs}, false, nil
359+
}
360+
361+
// Save the policy file
362+
if err := fileutils.WriteRename(policyPath, data); err != nil {
363+
return tpm2.PCRSelection{}, false, fmt.Errorf("failed to write sealing PCRs policy (id=%d, pcrs=%v) to %s: %w",
364+
sp.Id, sp.Pcrs, policyPath, err)
365+
}
366+
367+
// Save the hash file
368+
if err := fileutils.WriteRename(policyHashPath, policyHash); err != nil {
369+
return tpm2.PCRSelection{}, false, fmt.Errorf("failed to write sealing PCRs hash file: %w", err)
370+
}
371+
372+
return tpm2.PCRSelection{Hash: tpm2.AlgSHA256, PCRs: sp.Pcrs}, true, nil
373+
}
374+
262375
// GetTpmLogFileNames returns paths to saved TPM logs
263376
func GetTpmLogFileNames() (string, string) {
264377
return measurementLogSealSuccess, measurementLogUnsealFail
@@ -699,6 +812,7 @@ func FetchSealedVaultKey(log *base.LogObject) ([]byte, error) {
699812
//gain some knowledge about existing environment
700813
sealedKeyPresent := isSealedKeyPresent()
701814
legacyKeyPresent := isLegacyKeyPresent()
815+
pcrSelection := GetDiskKeySealingPCRs(log, PcrPolicyIndexesFile)
702816

703817
if !sealedKeyPresent && !legacyKeyPresent {
704818
log.Noticef("neither legacy nor sealed disk key present, generating a fresh key")
@@ -723,7 +837,7 @@ func FetchSealedVaultKey(log *base.LogObject) ([]byte, error) {
723837
if err != nil {
724838
return nil, fmt.Errorf("GetRandom failed: %w", err)
725839
}
726-
err = SealDiskKey(log, key, DiskKeySealingPCRs)
840+
err = SealDiskKey(log, key, pcrSelection)
727841
if err != nil {
728842
return nil, fmt.Errorf("sealing the fresh disk key failed: %w", err)
729843
}
@@ -750,7 +864,7 @@ func FetchSealedVaultKey(log *base.LogObject) ([]byte, error) {
750864

751865
log.Noticef("try to convert the legacy key into a sealed key")
752866

753-
err = SealDiskKey(log, key, DiskKeySealingPCRs)
867+
err = SealDiskKey(log, key, pcrSelection)
754868
if err != nil {
755869
return nil, fmt.Errorf("sealing the legacy disk key into TPM failed: %w", err)
756870
}
@@ -761,7 +875,7 @@ func FetchSealedVaultKey(log *base.LogObject) ([]byte, error) {
761875
log.Noticef("sealed disk key present int TPM, about to unseal it")
762876
}
763877
//at this point, we have a key sealed into TPM
764-
key, err := UnsealDiskKey(DiskKeySealingPCRs)
878+
key, err := UnsealDiskKey(pcrSelection)
765879
if err == nil {
766880
// be more verbose, lets celebrate
767881
log.Noticef("successfully unsealed the disk key from TPM")
@@ -1011,7 +1125,7 @@ func CompareLegacyandSealedKey() SealedKeyType {
10111125
//no cloning case, return SealedKeyTypeNew
10121126
return SealedKeyTypeNew
10131127
}
1014-
unsealedKey, err := UnsealDiskKey(DiskKeySealingPCRs)
1128+
unsealedKey, err := UnsealDiskKey(GetDiskKeySealingPCRs(nil, PcrPolicyIndexesFile))
10151129
if err != nil {
10161130
//key is present but can't unseal it
10171131
//but legacy key is present
@@ -1217,7 +1331,8 @@ func FindMismatchingPCRs() ([]int, error) {
12171331
// this should never happen, except when we update EVE and adding new
12181332
// indexes to the DiskKeySealingPCRs, anyways, better safe than sorry!
12191333
if !ok {
1220-
return nil, fmt.Errorf("saved PCR index %d doesn't exist at run-time PCRs list %v", i, DiskKeySealingPCRs.PCRs)
1334+
pcrSelection := GetDiskKeySealingPCRs(nil, PcrPolicyIndexesFile)
1335+
return nil, fmt.Errorf("saved PCR index %d doesn't exist at run-time PCRs list %v", i, pcrSelection.PCRs)
12211336
}
12221337

12231338
if !bytes.Equal(readPCR, savedPCR) {
@@ -1236,10 +1351,13 @@ func readDiskKeySealingPCRs() (map[int][]byte, error) {
12361351
}
12371352
defer rw.Close()
12381353

1354+
// Get the PCRs used in sealing the disk key
1355+
pcrSelection := GetDiskKeySealingPCRs(nil, PcrPolicyIndexesFile)
1356+
12391357
// tpm2.ReadPCRs returns at most 8 PCRs, so loop over and read one by one
12401358
readPCRs := make(map[int][]byte)
1241-
for _, v := range DiskKeySealingPCRs.PCRs {
1242-
p, err := tpm2.ReadPCR(rw, v, DiskKeySealingPCRs.Hash)
1359+
for _, v := range pcrSelection.PCRs {
1360+
p, err := tpm2.ReadPCR(rw, v, pcrSelection.Hash)
12431361
if err != nil {
12441362
return nil, err
12451363
}

0 commit comments

Comments
 (0)