From 166d75fe888d334349f57dcf405b6867ca5305e2 Mon Sep 17 00:00:00 2001 From: Noel Georgi Date: Fri, 14 Jul 2023 23:39:26 +0530 Subject: [PATCH] fix: tpm2 encrypt/decrypt flow The previous flow was using TPM PCR 11 values to bound the policy which means TPM cannot unseal when UKI changes. Now it's fixed to use PCR 7 which is bound to the SecureBoot state (SecureBoot status and Certificates). This provides a full chain of trust bound to SecureBoot state and signed PCR signature. Also the code has been refactored to use PolicyCalculator from the TPM library. Signed-off-by: Noel Georgi --- internal/app/init/main.go | 6 +- .../v1alpha1/v1alpha1_sequencer_tasks.go | 6 +- internal/pkg/encryption/keys/tpm2.go | 6 +- internal/pkg/mount/switchroot/switchroot.go | 6 +- .../measure/internal/pcr/bank_data.go | 12 +- .../measure/internal/pcr/bank_data_test.go | 4 +- .../secureboot/measure/internal/pcr/pcr.go | 25 - .../secureboot/measure/internal/pcr/policy.go | 34 - .../measure/internal/pcr/policy_test.go | 32 - internal/pkg/secureboot/measure/measure.go | 8 +- internal/pkg/secureboot/secureboot.go | 9 +- internal/pkg/secureboot/tpm2/keys.go | 72 ++ internal/pkg/secureboot/tpm2/pcr.go | 205 ++++++ .../internal/pcr => tpm2}/pcr_test.go | 7 +- internal/pkg/secureboot/tpm2/policy.go | 79 ++ internal/pkg/secureboot/tpm2/policy_test.go | 51 ++ internal/pkg/secureboot/tpm2/seal.go | 139 ++++ internal/pkg/secureboot/tpm2/signature.go | 50 ++ .../tpm2/testdata/pcr-signing-crt.pem | 14 + internal/pkg/secureboot/tpm2/tpm2.go | 14 + internal/pkg/secureboot/tpm2/unseal.go | 264 +++++++ internal/pkg/tpm2/tpm2.go | 697 ------------------ pkg/machinery/constants/constants.go | 3 - 23 files changed, 923 insertions(+), 820 deletions(-) delete mode 100644 internal/pkg/secureboot/measure/internal/pcr/pcr.go delete mode 100644 internal/pkg/secureboot/measure/internal/pcr/policy.go delete mode 100644 internal/pkg/secureboot/measure/internal/pcr/policy_test.go create mode 100644 internal/pkg/secureboot/tpm2/keys.go create mode 100644 internal/pkg/secureboot/tpm2/pcr.go rename internal/pkg/secureboot/{measure/internal/pcr => tpm2}/pcr_test.go (79%) create mode 100644 internal/pkg/secureboot/tpm2/policy.go create mode 100644 internal/pkg/secureboot/tpm2/policy_test.go create mode 100644 internal/pkg/secureboot/tpm2/seal.go create mode 100644 internal/pkg/secureboot/tpm2/signature.go create mode 100644 internal/pkg/secureboot/tpm2/testdata/pcr-signing-crt.pem create mode 100644 internal/pkg/secureboot/tpm2/tpm2.go create mode 100644 internal/pkg/secureboot/tpm2/unseal.go delete mode 100644 internal/pkg/tpm2/tpm2.go diff --git a/internal/app/init/main.go b/internal/app/init/main.go index fc67e73d8..6d0c98922 100644 --- a/internal/app/init/main.go +++ b/internal/app/init/main.go @@ -26,7 +26,7 @@ import ( "github.com/siderolabs/talos/internal/pkg/mount/switchroot" "github.com/siderolabs/talos/internal/pkg/rng" "github.com/siderolabs/talos/internal/pkg/secureboot" - "github.com/siderolabs/talos/internal/pkg/tpm2" + "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" "github.com/siderolabs/talos/pkg/machinery/constants" "github.com/siderolabs/talos/pkg/machinery/extensions" "github.com/siderolabs/talos/pkg/version" @@ -61,8 +61,8 @@ func run() (err error) { } // extend PCR 11 with enter-initrd - if err = tpm2.PCRExtent(constants.UKIMeasuredPCR, []byte(secureboot.EnterInitrd)); err != nil { - return fmt.Errorf("failed to extend PCR %d with enter-initrd: %v", constants.UKIMeasuredPCR, err) + if err = tpm2.PCRExtent(secureboot.UKIPCR, []byte(secureboot.EnterInitrd)); err != nil { + return fmt.Errorf("failed to extend PCR %d with enter-initrd: %v", secureboot.UKIPCR, err) } log.Printf("booting Talos %s", version.Tag) diff --git a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go index e91c0eb49..8f35fab3c 100644 --- a/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go +++ b/internal/app/machined/pkg/runtime/v1alpha1/v1alpha1_sequencer_tasks.go @@ -62,7 +62,7 @@ import ( "github.com/siderolabs/talos/internal/pkg/mount" "github.com/siderolabs/talos/internal/pkg/partition" "github.com/siderolabs/talos/internal/pkg/secureboot" - "github.com/siderolabs/talos/internal/pkg/tpm2" + "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" "github.com/siderolabs/talos/pkg/conditions" "github.com/siderolabs/talos/pkg/images" krnl "github.com/siderolabs/talos/pkg/kernel" @@ -832,7 +832,7 @@ func WriteUdevRules(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) { // StartMachined represents the task to start machined. func StartMachined(_ runtime.Sequence, _ any) (runtime.TaskExecutionFunc, string) { return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) error { - if err := tpm2.PCRExtent(constants.UKIMeasuredPCR, []byte(secureboot.EnterMachined)); err != nil { + if err := tpm2.PCRExtent(secureboot.UKIPCR, []byte(secureboot.EnterMachined)); err != nil { return err } @@ -889,7 +889,7 @@ func StartUdevd(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) { // ExtendPCRStartAll represents the task to extend the PCR with the StartTheWorld PCR phase. func ExtendPCRStartAll(runtime.Sequence, any) (runtime.TaskExecutionFunc, string) { return func(ctx context.Context, logger *log.Logger, r runtime.Runtime) (err error) { - return tpm2.PCRExtent(constants.UKIMeasuredPCR, []byte(secureboot.StartTheWorld)) + return tpm2.PCRExtent(secureboot.UKIPCR, []byte(secureboot.StartTheWorld)) }, "extendPCRStartAll" } diff --git a/internal/pkg/encryption/keys/tpm2.go b/internal/pkg/encryption/keys/tpm2.go index dfa8a13c3..ca21e49a7 100644 --- a/internal/pkg/encryption/keys/tpm2.go +++ b/internal/pkg/encryption/keys/tpm2.go @@ -14,8 +14,8 @@ import ( "github.com/siderolabs/go-blockdevice/blockdevice/encryption/luks" "github.com/siderolabs/go-blockdevice/blockdevice/encryption/token" - "github.com/siderolabs/talos/internal/pkg/tpm2" - "github.com/siderolabs/talos/pkg/machinery/constants" + "github.com/siderolabs/talos/internal/pkg/secureboot" + "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" ) // TPMToken is the userdata stored in the partition token metadata. @@ -59,7 +59,7 @@ func (h *TPMKeyHandler) NewKey(ctx context.Context) (*encryption.Key, token.Toke KeySlots: []int{h.slot}, SealedBlobPrivate: resp.SealedBlobPrivate, SealedBlobPublic: resp.SealedBlobPublic, - PCRs: []int{constants.UKIMeasuredPCR}, + PCRs: []int{secureboot.UKIPCR}, Alg: "sha256", PolicyHash: resp.PolicyDigest, KeyName: resp.KeyName, diff --git a/internal/pkg/mount/switchroot/switchroot.go b/internal/pkg/mount/switchroot/switchroot.go index c4f9bf839..a84086ab3 100644 --- a/internal/pkg/mount/switchroot/switchroot.go +++ b/internal/pkg/mount/switchroot/switchroot.go @@ -15,7 +15,7 @@ import ( "github.com/siderolabs/talos/internal/pkg/mount" "github.com/siderolabs/talos/internal/pkg/secureboot" - "github.com/siderolabs/talos/internal/pkg/tpm2" + "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" "github.com/siderolabs/talos/pkg/machinery/constants" ) @@ -69,8 +69,8 @@ func Switch(prefix string, mountpoints *mount.Points) (err error) { } // extend PCR 11 with leave-initrd - if err = tpm2.PCRExtent(constants.UKIMeasuredPCR, []byte(secureboot.LeaveInitrd)); err != nil { - return fmt.Errorf("failed to extend PCR %d with leave-initrd: %v", constants.UKIMeasuredPCR, err) + if err = tpm2.PCRExtent(secureboot.UKIPCR, []byte(secureboot.LeaveInitrd)); err != nil { + return fmt.Errorf("failed to extend PCR %d with leave-initrd: %v", secureboot.UKIPCR, err) } // Note that /sbin/init is machined. We call it init since this is the diff --git a/internal/pkg/secureboot/measure/internal/pcr/bank_data.go b/internal/pkg/secureboot/measure/internal/pcr/bank_data.go index 710fc15aa..e9c82f970 100644 --- a/internal/pkg/secureboot/measure/internal/pcr/bank_data.go +++ b/internal/pkg/secureboot/measure/internal/pcr/bank_data.go @@ -15,13 +15,13 @@ import ( "github.com/google/go-tpm/tpm2" "github.com/siderolabs/talos/internal/pkg/secureboot" - talostpm2 "github.com/siderolabs/talos/internal/pkg/tpm2" + tpm2internal "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" ) // CalculateBankData calculates the PCR bank data for a given set of UKI file sections. // // This mimics the process happening happening in the TPM when the UKI is being loaded. -func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureboot.Section]string, rsaKey *rsa.PrivateKey) ([]talostpm2.BankData, error) { +func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureboot.Section]string, rsaKey *rsa.PrivateKey) ([]tpm2internal.BankData, error) { // get fingerprint of public key pubKeyFingerprint := sha256.Sum256(x509.MarshalPKCS1PublicKey(&rsaKey.PublicKey)) @@ -30,7 +30,7 @@ func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureb return nil, err } - pcrSelector, err := CreateSelector([]int{secureboot.UKIPCR}) + pcrSelector, err := tpm2internal.CreateSelector([]int{secureboot.UKIPCR}) if err != nil { return nil, fmt.Errorf("failed to create PCR selection: %v", err) } @@ -59,7 +59,7 @@ func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureb } } - banks := make([]talostpm2.BankData, 0) + banks := make([]tpm2internal.BankData, 0) for _, phaseInfo := range secureboot.OrderedPhases() { // extend always, but only calculate signature if requested @@ -71,7 +71,7 @@ func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureb hash := hashData.Hash() - policyPCR, err := CalculatePolicy(hash, pcrSelection) + policyPCR, err := tpm2internal.CalculatePolicy(hash, pcrSelection) if err != nil { return nil, err } @@ -81,7 +81,7 @@ func CalculateBankData(pcrNumber int, alg tpm2.TPMAlgID, sectionData map[secureb return nil, err } - banks = append(banks, talostpm2.BankData{ + banks = append(banks, tpm2internal.BankData{ PCRs: []int{pcrNumber}, PKFP: hex.EncodeToString(pubKeyFingerprint[:]), Sig: sigData.SignatureBase64, diff --git a/internal/pkg/secureboot/measure/internal/pcr/bank_data_test.go b/internal/pkg/secureboot/measure/internal/pcr/bank_data_test.go index 15cab198b..b65044754 100644 --- a/internal/pkg/secureboot/measure/internal/pcr/bank_data_test.go +++ b/internal/pkg/secureboot/measure/internal/pcr/bank_data_test.go @@ -15,7 +15,7 @@ import ( "github.com/siderolabs/talos/internal/pkg/secureboot" "github.com/siderolabs/talos/internal/pkg/secureboot/measure/internal/pcr" - talostpm2 "github.com/siderolabs/talos/internal/pkg/tpm2" + tpm2internal "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" ) func TestCalculateBankData(t *testing.T) { @@ -40,7 +40,7 @@ func TestCalculateBankData(t *testing.T) { require.NoError(t, err) require.Equal(t, - []talostpm2.BankData{ + []tpm2internal.BankData{ { PCRs: []int{15}, PKFP: "58f58f625bd8a8b6681e4b40688cf99b26419b6b2c5f6e14a2c7c67a3b0b1620", diff --git a/internal/pkg/secureboot/measure/internal/pcr/pcr.go b/internal/pkg/secureboot/measure/internal/pcr/pcr.go deleted file mode 100644 index 998ac5e74..000000000 --- a/internal/pkg/secureboot/measure/internal/pcr/pcr.go +++ /dev/null @@ -1,25 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// Package pcr contains code that handles PCR operations. -package pcr - -import "fmt" - -// CreateSelector converts PCR numbers into a bitmask. -func CreateSelector(pcrs []int) ([]byte, error) { - const sizeOfPCRSelect = 3 - - mask := make([]byte, sizeOfPCRSelect) - - for _, n := range pcrs { - if n >= 8*sizeOfPCRSelect { - return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1) - } - - mask[n>>3] |= 1 << (n & 0x7) - } - - return mask, nil -} diff --git a/internal/pkg/secureboot/measure/internal/pcr/policy.go b/internal/pkg/secureboot/measure/internal/pcr/policy.go deleted file mode 100644 index 268fb01c5..000000000 --- a/internal/pkg/secureboot/measure/internal/pcr/policy.go +++ /dev/null @@ -1,34 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -package pcr - -import ( - "crypto/sha256" - - "github.com/google/go-tpm/tpm2" -) - -// CalculatePolicy calculates the policy hash for a given PCR value and PCR selection. -func CalculatePolicy(pcrValue []byte, pcrSelection tpm2.TPMLPCRSelection) ([]byte, error) { - calculator, err := tpm2.NewPolicyCalculator(tpm2.TPMAlgSHA256) - if err != nil { - return nil, err - } - - pcrHash := sha256.Sum256(pcrValue) - - policy := tpm2.PolicyPCR{ - PcrDigest: tpm2.TPM2BDigest{ - Buffer: pcrHash[:], - }, - Pcrs: pcrSelection, - } - - if err := policy.Update(calculator); err != nil { - return nil, err - } - - return calculator.Hash().Digest, nil -} diff --git a/internal/pkg/secureboot/measure/internal/pcr/policy_test.go b/internal/pkg/secureboot/measure/internal/pcr/policy_test.go deleted file mode 100644 index 1eb80d076..000000000 --- a/internal/pkg/secureboot/measure/internal/pcr/policy_test.go +++ /dev/null @@ -1,32 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -package pcr_test - -import ( - "testing" - - "github.com/google/go-tpm/tpm2" - "github.com/stretchr/testify/require" - - "github.com/siderolabs/talos/internal/pkg/secureboot/measure/internal/pcr" -) - -func TestCalculatePolicy(t *testing.T) { - t.Parallel() - - policy, err := pcr.CalculatePolicy([]byte{1, 3, 5}, tpm2.TPMLPCRSelection{ - PCRSelections: []tpm2.TPMSPCRSelection{ - { - Hash: tpm2.TPMAlgSHA256, - PCRSelect: []byte{10, 11, 12}, - }, - }, - }) - require.NoError(t, err) - require.Equal(t, - []byte{0x84, 0xd6, 0x51, 0x47, 0xb0, 0x53, 0x94, 0xd0, 0xfa, 0xc4, 0x5e, 0x36, 0x0, 0x20, 0x3e, 0x3a, 0x11, 0x1, 0x27, 0xfb, 0xe2, 0x6f, 0xc1, 0xe3, 0x3, 0x3, 0x10, 0x21, 0x33, 0xf9, 0x15, 0xe3}, - policy, - ) -} diff --git a/internal/pkg/secureboot/measure/measure.go b/internal/pkg/secureboot/measure/measure.go index eb9fa5a20..7142241df 100644 --- a/internal/pkg/secureboot/measure/measure.go +++ b/internal/pkg/secureboot/measure/measure.go @@ -18,7 +18,7 @@ import ( "github.com/siderolabs/talos/internal/pkg/secureboot" "github.com/siderolabs/talos/internal/pkg/secureboot/measure/internal/pcr" - talostpm2 "github.com/siderolabs/talos/internal/pkg/tpm2" + tpm2internal "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" ) // SectionsData holds a map of Section to file path to the corresponding section. @@ -45,17 +45,17 @@ func loadRSAKey(path string) (*rsa.PrivateKey, error) { } // GenerateSignedPCR generates the PCR signed data for a given set of UKI file sections. -func GenerateSignedPCR(sectionsData SectionsData, rsaKeyPath string) (*talostpm2.PCRData, error) { +func GenerateSignedPCR(sectionsData SectionsData, rsaKeyPath string) (*tpm2internal.PCRData, error) { rsaKey, err := loadRSAKey(rsaKeyPath) if err != nil { return nil, err } - data := &talostpm2.PCRData{} + data := &tpm2internal.PCRData{} for _, algo := range []struct { alg tpm2.TPMAlgID - bankDataSetter *[]talostpm2.BankData + bankDataSetter *[]tpm2internal.BankData }{ { alg: tpm2.TPMAlgSHA1, diff --git a/internal/pkg/secureboot/secureboot.go b/internal/pkg/secureboot/secureboot.go index ed6b301b4..d5e2b3d67 100644 --- a/internal/pkg/secureboot/secureboot.go +++ b/internal/pkg/secureboot/secureboot.go @@ -78,5 +78,10 @@ func OrderedPhases() []PhaseInfo { } } -// UKIPCR is the PCR number where sections except `.pcrsig` are measured. -const UKIPCR = 11 +const ( + // UKIPCR is the PCR number where sections except `.pcrsig` are measured. + UKIPCR = 11 + // SecureBootStatePCR is the PCR number where the secure boot state and the signature are measured. + // PCR 7 changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) are updated. + SecureBootStatePCR = 7 +) diff --git a/internal/pkg/secureboot/tpm2/keys.go b/internal/pkg/secureboot/tpm2/keys.go new file mode 100644 index 000000000..1c31c77a6 --- /dev/null +++ b/internal/pkg/secureboot/tpm2/keys.go @@ -0,0 +1,72 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2 + +import ( + "crypto/rsa" + "crypto/x509" + "encoding/pem" + "fmt" + "os" + + "github.com/google/go-tpm/tpm2" +) + +// ParsePCRSigningPubKey parses a PEM encoded RSA public key. +func ParsePCRSigningPubKey(file string) (*rsa.PublicKey, error) { + pcrSigningPubKey, err := os.ReadFile(file) + if err != nil { + return nil, fmt.Errorf("failed to read pcr signing public key: %v", err) + } + + block, _ := pem.Decode(pcrSigningPubKey) + if block == nil { + return nil, fmt.Errorf("failed to decode pcr signing public key") + } + + // parse rsa public key + tpm2PubKeyAny, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return nil, err + } + + tpm2PubKey, ok := tpm2PubKeyAny.(*rsa.PublicKey) + if !ok { + return nil, fmt.Errorf("failed to cast pcr signing public key to rsa") + } + + return tpm2PubKey, nil +} + +// RSAPubKeyTemplate returns a TPM2.0 public key template for RSA keys. +func RSAPubKeyTemplate(bitlen, exponent int, modulus []byte) tpm2.TPMTPublic { + return tpm2.TPMTPublic{ + Type: tpm2.TPMAlgRSA, + NameAlg: tpm2.TPMAlgSHA256, + ObjectAttributes: tpm2.TPMAObject{ + Decrypt: true, + SignEncrypt: true, + UserWithAuth: true, + }, + Parameters: tpm2.NewTPMUPublicParms(tpm2.TPMAlgRSA, &tpm2.TPMSRSAParms{ + Symmetric: tpm2.TPMTSymDefObject{ + Algorithm: tpm2.TPMAlgNull, + Mode: tpm2.NewTPMUSymMode(tpm2.TPMAlgRSA, tpm2.TPMAlgNull), + }, + Scheme: tpm2.TPMTRSAScheme{ + Scheme: tpm2.TPMAlgNull, + Details: tpm2.NewTPMUAsymScheme(tpm2.TPMAlgRSA, &tpm2.TPMSSigSchemeRSASSA{ + HashAlg: tpm2.TPMAlgNull, + }), + }, + KeyBits: tpm2.TPMKeyBits(bitlen), + Exponent: uint32(exponent), + }), + Unique: tpm2.NewTPMUPublicID(tpm2.TPMAlgRSA, &tpm2.TPM2BPublicKeyRSA{ + Buffer: modulus, + }), + } +} diff --git a/internal/pkg/secureboot/tpm2/pcr.go b/internal/pkg/secureboot/tpm2/pcr.go new file mode 100644 index 000000000..308275563 --- /dev/null +++ b/internal/pkg/secureboot/tpm2/pcr.go @@ -0,0 +1,205 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2 + +import ( + "bytes" + "crypto" + "crypto/sha256" + "fmt" + "log" + "os" + + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpm2/transport" + + "github.com/siderolabs/talos/internal/pkg/secureboot" +) + +// CreateSelector converts PCR numbers into a bitmask. +func CreateSelector(pcrs []int) ([]byte, error) { + const sizeOfPCRSelect = 3 + + mask := make([]byte, sizeOfPCRSelect) + + for _, n := range pcrs { + if n >= 8*sizeOfPCRSelect { + return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1) + } + + mask[n>>3] |= 1 << (n & 0x7) + } + + return mask, nil +} + +// ReadPCR reads the value of a single PCR. +func ReadPCR(t transport.TPM, pcr int) ([]byte, error) { + pcrSelector, err := CreateSelector([]int{pcr}) + if err != nil { + return nil, fmt.Errorf("failed to create PCR selection: %v", err) + } + + pcrRead := tpm2.PCRRead{ + PCRSelectionIn: tpm2.TPMLPCRSelection{ + PCRSelections: []tpm2.TPMSPCRSelection{ + { + Hash: tpm2.TPMAlgSHA256, + PCRSelect: pcrSelector, + }, + }, + }, + } + + pcrValue, err := pcrRead.Execute(t) + if err != nil { + return nil, fmt.Errorf("failed to read PCR: %v", err) + } + + return pcrValue.PCRValues.Digests[0].Buffer, nil +} + +// PCRExtent hashes the input and extends the PCR with the hash. +func PCRExtent(pcr int, data []byte) error { + t, err := transport.OpenTPM() + if err != nil { + if os.IsNotExist(err) { + log.Printf("TPM device is not available, skipping PCR extension") + + return nil + } + + return err + } + + defer t.Close() // nolint: errcheck + + // since we are using SHA256, we can assume that the PCR bank is SHA256 + digest := sha256.Sum256(data) + + pcrHandle := tpm2.PCRExtend{ + PCRHandle: tpm2.AuthHandle{ + Handle: tpm2.TPMHandle(pcr), + Auth: tpm2.PasswordAuth(nil), + }, + Digests: tpm2.TPMLDigestValues{ + Digests: []tpm2.TPMTHA{ + { + HashAlg: tpm2.TPMAlgSHA256, + Digest: digest[:], + }, + }, + }, + } + + if _, err = pcrHandle.Execute(t); err != nil { + return err + } + + return nil +} + +// PolicyPCRDigest executes policyPCR and returns the digest. +func PolicyPCRDigest(t transport.TPM, policyHandle tpm2.TPMHandle, pcrSelection tpm2.TPMLPCRSelection) (*tpm2.TPM2BDigest, error) { + policyPCR := tpm2.PolicyPCR{ + PolicySession: policyHandle, + Pcrs: pcrSelection, + } + + if _, err := policyPCR.Execute(t); err != nil { + return nil, fmt.Errorf("failed to execute policyPCR: %v", err) + } + + policyGetDigest := tpm2.PolicyGetDigest{ + PolicySession: policyHandle, + } + + policyGetDigestResponse, err := policyGetDigest.Execute(t) + if err != nil { + return nil, fmt.Errorf("failed to get policy digest: %v", err) + } + + return &policyGetDigestResponse.PolicyDigest, nil +} + +// nolint:gocyclo +func validatePCRBanks(t transport.TPM) error { + pcrValue, err := ReadPCR(t, secureboot.UKIPCR) + if err != nil { + return fmt.Errorf("failed to read PCR: %v", err) + } + + if err = validatePCRNotZeroAndNotFilled(pcrValue, secureboot.UKIPCR); err != nil { + return err + } + + pcrValue, err = ReadPCR(t, secureboot.SecureBootStatePCR) + if err != nil { + return fmt.Errorf("failed to read PCR: %v", err) + } + + if err = validatePCRNotZeroAndNotFilled(pcrValue, secureboot.SecureBootStatePCR); err != nil { + return err + } + + caps := tpm2.GetCapability{ + Capability: tpm2.TPMCapPCRs, + Property: 0, + PropertyCount: 1, + } + + capsResp, err := caps.Execute(t) + if err != nil { + return fmt.Errorf("failed to get PCR capabilities: %v", err) + } + + assignedPCRs, err := capsResp.CapabilityData.Data.AssignedPCR() + if err != nil { + return fmt.Errorf("failed to parse assigned PCRs: %v", err) + } + + for _, s := range assignedPCRs.PCRSelections { + h, err := s.Hash.Hash() + if err != nil { + return fmt.Errorf("failed to parse hash algorithm: %v", err) + } + + switch h { //nolint:exhaustive + case crypto.SHA1: + continue + case crypto.SHA256: + // check if 24 banks are available + if len(s.PCRSelect) != 24/8 { + return fmt.Errorf("unexpected number of PCR banks: %d", len(s.PCRSelect)) + } + + // check if all banks are available + if s.PCRSelect[0] != 0xff || s.PCRSelect[1] != 0xff || s.PCRSelect[2] != 0xff { + return fmt.Errorf("unexpected PCR banks: %v", s.PCRSelect) + } + case crypto.SHA384: + continue + case crypto.SHA512: + continue + default: + return fmt.Errorf("unsupported hash algorithm: %s", h.String()) + } + } + + return nil +} + +func validatePCRNotZeroAndNotFilled(pcrValue []byte, pcr int) error { + if bytes.Equal(pcrValue, bytes.Repeat([]byte{0x00}, sha256.Size)) { + return fmt.Errorf("PCR bank %d is populated with all zeroes", pcr) + } + + if bytes.Equal(pcrValue, bytes.Repeat([]byte{0xFF}, sha256.Size)) { + return fmt.Errorf("PCR bank %d is populated with all 0xFF", pcr) + } + + return nil +} diff --git a/internal/pkg/secureboot/measure/internal/pcr/pcr_test.go b/internal/pkg/secureboot/tpm2/pcr_test.go similarity index 79% rename from internal/pkg/secureboot/measure/internal/pcr/pcr_test.go rename to internal/pkg/secureboot/tpm2/pcr_test.go index 3331294ca..115347b24 100644 --- a/internal/pkg/secureboot/measure/internal/pcr/pcr_test.go +++ b/internal/pkg/secureboot/tpm2/pcr_test.go @@ -2,14 +2,15 @@ // License, v. 2.0. If a copy of the MPL was not distributed with this // file, You can obtain one at http://mozilla.org/MPL/2.0/. -package pcr_test +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2_test import ( "testing" "github.com/stretchr/testify/require" - "github.com/siderolabs/talos/internal/pkg/secureboot/measure/internal/pcr" + tpm2internal "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" ) func TestGetSelection(t *testing.T) { @@ -40,7 +41,7 @@ func TestGetSelection(t *testing.T) { t.Run(tt.name, func(t *testing.T) { t.Parallel() - actual, err := pcr.CreateSelector(tt.pcrs) + actual, err := tpm2internal.CreateSelector(tt.pcrs) require.NoError(t, err) require.Equal(t, tt.expected, actual) diff --git a/internal/pkg/secureboot/tpm2/policy.go b/internal/pkg/secureboot/tpm2/policy.go new file mode 100644 index 000000000..f6491bcfd --- /dev/null +++ b/internal/pkg/secureboot/tpm2/policy.go @@ -0,0 +1,79 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2 + +import ( + "crypto/sha256" + "fmt" + + "github.com/google/go-tpm/tpm2" +) + +// CalculatePolicy calculates the policy hash for a given PCR value and PCR selection. +func CalculatePolicy(pcrValue []byte, pcrSelection tpm2.TPMLPCRSelection) ([]byte, error) { + calculator, err := tpm2.NewPolicyCalculator(tpm2.TPMAlgSHA256) + if err != nil { + return nil, err + } + + pcrHash := sha256.Sum256(pcrValue) + + policy := tpm2.PolicyPCR{ + PcrDigest: tpm2.TPM2BDigest{ + Buffer: pcrHash[:], + }, + Pcrs: pcrSelection, + } + + if err := policy.Update(calculator); err != nil { + return nil, err + } + + return calculator.Hash().Digest, nil +} + +// CalculateSealingPolicyDigest calculates the sealing policy digest for a given PCR value, PCR selection and public key. +func CalculateSealingPolicyDigest(pcrValue []byte, pcrSelection tpm2.TPMLPCRSelection, pubKey string) ([]byte, error) { + calculator, err := tpm2.NewPolicyCalculator(tpm2.TPMAlgSHA256) + if err != nil { + return nil, err + } + + pubKeyData, err := ParsePCRSigningPubKey(pubKey) + if err != nil { + return nil, err + } + + publicKeyTemplate := RSAPubKeyTemplate(pubKeyData.N.BitLen(), pubKeyData.E, pubKeyData.N.Bytes()) + + name, err := tpm2.ObjectName(&publicKeyTemplate) + if err != nil { + return nil, fmt.Errorf("failed to calculate name: %v", err) + } + + policyAuthorize := tpm2.PolicyAuthorize{ + KeySign: *name, + } + + if err := policyAuthorize.Update(calculator); err != nil { + return nil, err + } + + pcrHash := sha256.Sum256(pcrValue) + + policy := tpm2.PolicyPCR{ + PcrDigest: tpm2.TPM2BDigest{ + Buffer: pcrHash[:], + }, + Pcrs: pcrSelection, + } + + if err := policy.Update(calculator); err != nil { + return nil, err + } + + return calculator.Hash().Digest, nil +} diff --git a/internal/pkg/secureboot/tpm2/policy_test.go b/internal/pkg/secureboot/tpm2/policy_test.go new file mode 100644 index 000000000..15264ccd0 --- /dev/null +++ b/internal/pkg/secureboot/tpm2/policy_test.go @@ -0,0 +1,51 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2_test + +import ( + "testing" + + "github.com/google/go-tpm/tpm2" + "github.com/stretchr/testify/require" + + tpm2internal "github.com/siderolabs/talos/internal/pkg/secureboot/tpm2" +) + +func TestCalculatePolicy(t *testing.T) { + t.Parallel() + + policy, err := tpm2internal.CalculatePolicy([]byte{1, 3, 5}, tpm2.TPMLPCRSelection{ + PCRSelections: []tpm2.TPMSPCRSelection{ + { + Hash: tpm2.TPMAlgSHA256, + PCRSelect: []byte{10, 11, 12}, + }, + }, + }) + require.NoError(t, err) + require.Equal(t, + []byte{0x84, 0xd6, 0x51, 0x47, 0xb0, 0x53, 0x94, 0xd0, 0xfa, 0xc4, 0x5e, 0x36, 0x0, 0x20, 0x3e, 0x3a, 0x11, 0x1, 0x27, 0xfb, 0xe2, 0x6f, 0xc1, 0xe3, 0x3, 0x3, 0x10, 0x21, 0x33, 0xf9, 0x15, 0xe3}, + policy, + ) +} + +func TestCalculateSealingPolicyDigest(t *testing.T) { + t.Parallel() + + calculated, err := tpm2internal.CalculateSealingPolicyDigest([]byte{1, 3, 5}, tpm2.TPMLPCRSelection{ + PCRSelections: []tpm2.TPMSPCRSelection{ + { + Hash: tpm2.TPMAlgSHA256, + PCRSelect: []byte{10, 11, 12}, + }, + }, + }, "testdata/pcr-signing-crt.pem") + require.NoError(t, err) + require.Equal(t, + []byte{0xa0, 0xf4, 0x29, 0x7a, 0x6c, 0x1a, 0xc8, 0xcf, 0x88, 0x48, 0x8b, 0xcf, 0x63, 0x20, 0xdc, 0x2e, 0x75, 0xc8, 0x2, 0xa1, 0xb4, 0x62, 0x5a, 0xdc, 0x9a, 0xfb, 0x2a, 0x1a, 0x8a, 0xd2, 0xf0, 0x53}, + calculated, + ) +} diff --git a/internal/pkg/secureboot/tpm2/seal.go b/internal/pkg/secureboot/tpm2/seal.go new file mode 100644 index 000000000..97201ef7a --- /dev/null +++ b/internal/pkg/secureboot/tpm2/seal.go @@ -0,0 +1,139 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2 + +import ( + "fmt" + + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpm2/transport" + + "github.com/siderolabs/talos/internal/pkg/secureboot" + "github.com/siderolabs/talos/pkg/machinery/constants" +) + +// Seal seals the key using TPM2.0. +func Seal(key []byte) (*SealedResponse, error) { + t, err := transport.OpenTPM() + if err != nil { + return nil, err + } + defer t.Close() // nolint: errcheck + + // fail early if PCR banks are not present or filled with all zeroes or 0xff + if err = validatePCRBanks(t); err != nil { + return nil, err + } + + sealingPolicyDigest, err := calculateSealingPolicyDigest(t) + if err != nil { + return nil, err + } + + primary := tpm2.CreatePrimary{ + PrimaryHandle: tpm2.TPMRHOwner, + InPublic: tpm2.New2B(tpm2.ECCSRKTemplate), + } + + createPrimaryResponse, err := primary.Execute(t) + if err != nil { + return nil, err + } + + defer func() { + flush := tpm2.FlushContext{ + FlushHandle: createPrimaryResponse.ObjectHandle, + } + + _, flushErr := flush.Execute(t) + if flushErr != nil { + err = flushErr + } + }() + + outPub, err := createPrimaryResponse.OutPublic.Contents() + if err != nil { + return nil, err + } + + create := tpm2.Create{ + ParentHandle: tpm2.AuthHandle{ + Handle: createPrimaryResponse.ObjectHandle, + Name: createPrimaryResponse.Name, + Auth: tpm2.HMAC( + tpm2.TPMAlgSHA256, + 20, + tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub), + tpm2.AESEncryption(128, tpm2.EncryptInOut), + ), + }, + InSensitive: tpm2.TPM2BSensitiveCreate{ + Sensitive: &tpm2.TPMSSensitiveCreate{ + Data: tpm2.NewTPMUSensitiveCreate(&tpm2.TPM2BSensitiveData{ + Buffer: key, + }), + }, + }, + InPublic: tpm2.New2B(tpm2.TPMTPublic{ + Type: tpm2.TPMAlgKeyedHash, + NameAlg: tpm2.TPMAlgSHA256, + ObjectAttributes: tpm2.TPMAObject{ + FixedTPM: true, + FixedParent: true, + }, + Parameters: tpm2.NewTPMUPublicParms(tpm2.TPMAlgKeyedHash, &tpm2.TPMSKeyedHashParms{ + Scheme: tpm2.TPMTKeyedHashScheme{ + Scheme: tpm2.TPMAlgNull, + }, + }), + AuthPolicy: tpm2.TPM2BDigest{ + Buffer: sealingPolicyDigest, + }, + }), + } + + createResp, err := create.Execute(t) + if err != nil { + return nil, err + } + + resp := SealedResponse{ + SealedBlobPrivate: tpm2.Marshal(createResp.OutPrivate), + SealedBlobPublic: tpm2.Marshal(createResp.OutPublic), + KeyName: tpm2.Marshal(createPrimaryResponse.Name), + PolicyDigest: sealingPolicyDigest, + } + + return &resp, nil +} + +func calculateSealingPolicyDigest(t transport.TPM) ([]byte, error) { + pcrSelector, err := CreateSelector([]int{secureboot.SecureBootStatePCR}) + if err != nil { + return nil, fmt.Errorf("failed to create PCR selection: %v", err) + } + + pcrSelection := tpm2.TPMLPCRSelection{ + PCRSelections: []tpm2.TPMSPCRSelection{ + { + Hash: tpm2.TPMAlgSHA256, + PCRSelect: pcrSelector, + }, + }, + } + + pcrValue, err := ReadPCR(t, secureboot.SecureBootStatePCR) + if err != nil { + return nil, err + } + + sealingDigest, err := CalculateSealingPolicyDigest(pcrValue, pcrSelection, constants.PCRPublicKey) + if err != nil { + return nil, fmt.Errorf("failed to calculate sealing policy digest: %v", err) + } + + return sealingDigest, nil +} diff --git a/internal/pkg/secureboot/tpm2/signature.go b/internal/pkg/secureboot/tpm2/signature.go new file mode 100644 index 000000000..3cf5ad5c4 --- /dev/null +++ b/internal/pkg/secureboot/tpm2/signature.go @@ -0,0 +1,50 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2 + +import ( + "encoding/json" + "fmt" + "os" + + "github.com/siderolabs/talos/pkg/machinery/constants" +) + +// PCRData is the data structure for PCR signature json. +type PCRData struct { + SHA1 []BankData `json:"sha1,omitempty"` + SHA256 []BankData `json:"sha256,omitempty"` + SHA384 []BankData `json:"sha384,omitempty"` + SHA512 []BankData `json:"sha512,omitempty"` +} + +// BankData constains data for a specific PCR bank. +type BankData struct { + // list of PCR banks + PCRs []int `json:"pcrs"` + // Public key of the TPM + PKFP string `json:"pkfp"` + // Policy digest + Pol string `json:"pol"` + // Signature of the policy digest in base64 + Sig string `json:"sig"` +} + +// ParsePCRSignature parses the PCR signature json file. +func ParsePCRSignature() (*PCRData, error) { + pcrSignature, err := os.ReadFile(constants.PCRSignatureJSON) + if err != nil { + return nil, fmt.Errorf("failed to read pcr signature: %v", err) + } + + pcrData := &PCRData{} + + if err = json.Unmarshal(pcrSignature, pcrData); err != nil { + return nil, fmt.Errorf("failed to unmarshal pcr signature: %v", err) + } + + return pcrData, nil +} diff --git a/internal/pkg/secureboot/tpm2/testdata/pcr-signing-crt.pem b/internal/pkg/secureboot/tpm2/testdata/pcr-signing-crt.pem new file mode 100644 index 000000000..60d4744c4 --- /dev/null +++ b/internal/pkg/secureboot/tpm2/testdata/pcr-signing-crt.pem @@ -0,0 +1,14 @@ +-----BEGIN PUBLIC KEY----- +MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEA7qhAkdtZxqkIP79DDGin +9eaJBeNlJsClJTcbaXbNfk2QJGT3lqo9ErXQQftwYWLGo+kVd8puhnHGPkLW9apT +1/ZmUJEFwxV5xws0RllGVPhUga+1oubHUqhEiy707S4RrUEMk/o9wqmtnl2hY5Fx +MeQn2o7xrpcNhm8FtHpvQrT0MsbC1cS1ytZH/hwPy/QIB9bx+ugOha6wtQBnpgix +1BhHC/NwDIYPg+ONpQSCu9gkXVtLGlKfmjscUANQtBuVKa5NflrjkHw7NAdKYdKp +Mnmzr0yu6Tn/2oNmUiJAwHz0BXpfb4Yn8n/IoKJQ5Tv1g6d30wxxpBd0lbwSe9ML +RchIDJ5aFRybyRxaPGT17U3yEVzbV78kIFtocaqkc1ise8remZ0wxHzuolbTZD6o +swt7C9jMLvfMAQ7JtENXrpDM//XzdRLzyTWKOjhG0YmKKRY6cIrPkugM0PHGCE3R +MSH1FmPMrWWBNAMwS0Zba0Wm1b7vdw5fKeE8txH+IpA3IaE9AytYk0ig98ZgmXmB +V0sgxmJ/94scEF+sDg65LIkSEJMzf6q30UghbJJoP7eKOoDX9KBrR+POEsWm/EcU +5jTEQTHMU+qKtj5KD6TUn8R8yi4wCnyZ7uJLUqm8Ou8MzEZWbrsrbMvrewPDAHn0 +QQvb2tDtBgn6oH192jpkzckCAwEAAQ== +-----END PUBLIC KEY----- diff --git a/internal/pkg/secureboot/tpm2/tpm2.go b/internal/pkg/secureboot/tpm2/tpm2.go new file mode 100644 index 000000000..89d1d7165 --- /dev/null +++ b/internal/pkg/secureboot/tpm2/tpm2.go @@ -0,0 +1,14 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2 + +// SealedResponse is the response from the TPM2.0 Seal operation. +type SealedResponse struct { + SealedBlobPrivate []byte + SealedBlobPublic []byte + KeyName []byte + PolicyDigest []byte +} diff --git a/internal/pkg/secureboot/tpm2/unseal.go b/internal/pkg/secureboot/tpm2/unseal.go new file mode 100644 index 000000000..4010037ee --- /dev/null +++ b/internal/pkg/secureboot/tpm2/unseal.go @@ -0,0 +1,264 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. + +// Package tpm2 provides TPM2.0 related functionality helpers. +package tpm2 + +import ( + "bytes" + "crypto/sha256" + "crypto/x509" + "encoding/base64" + "encoding/hex" + "fmt" + + "github.com/google/go-tpm/tpm2" + "github.com/google/go-tpm/tpm2/transport" + + "github.com/siderolabs/talos/internal/pkg/secureboot" + "github.com/siderolabs/talos/pkg/machinery/constants" +) + +// Unseal unseals a sealed blob using the TPM +// nolint:gocyclo,cyclop +func Unseal(sealed SealedResponse) ([]byte, error) { + t, err := transport.OpenTPM() + if err != nil { + return nil, err + } + defer t.Close() // nolint: errcheck + + // fail early if PCR banks are not present or filled with all zeroes or 0xff + if err = validatePCRBanks(t); err != nil { + return nil, err + } + + tpmPub, err := tpm2.Unmarshal[tpm2.TPM2BPublic](sealed.SealedBlobPublic) + if err != nil { + return nil, err + } + + tpmPriv, err := tpm2.Unmarshal[tpm2.TPM2BPrivate](sealed.SealedBlobPrivate) + if err != nil { + return nil, err + } + + srk, err := tpm2.Unmarshal[tpm2.TPM2BName](sealed.KeyName) + if err != nil { + return nil, err + } + + // we need to create a primary since we don't persist the SRK + primary := tpm2.CreatePrimary{ + PrimaryHandle: tpm2.TPMRHOwner, + InPublic: tpm2.New2B(tpm2.ECCSRKTemplate), + } + + createPrimaryResponse, err := primary.Execute(t) + if err != nil { + return nil, err + } + + defer func() { + flush := tpm2.FlushContext{ + FlushHandle: createPrimaryResponse.ObjectHandle, + } + + _, flushErr := flush.Execute(t) + if flushErr != nil { + err = flushErr + } + }() + + outPub, err := createPrimaryResponse.OutPublic.Contents() + if err != nil { + return nil, err + } + + if !bytes.Equal(createPrimaryResponse.Name.Buffer, srk.Buffer) { + // this means the srk name does not match, possibly due to a different TPM or tpm was reset + // could also mean the disk was used on a different machine + return nil, fmt.Errorf("srk name does not match") + } + + load := tpm2.Load{ + ParentHandle: tpm2.NamedHandle{ + Handle: createPrimaryResponse.ObjectHandle, + Name: createPrimaryResponse.Name, + }, + InPrivate: *tpmPriv, + InPublic: *tpmPub, + } + + loadResponse, err := load.Execute(t) + if err != nil { + return nil, err + } + + policySess, policyCloseFunc, err := tpm2.PolicySession( + t, + tpm2.TPMAlgSHA256, + 20, + tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub), + ) + if err != nil { + return nil, fmt.Errorf("failed to create policy session: %v", err) + } + + defer policyCloseFunc() // nolint: errcheck + + pubKey, err := ParsePCRSigningPubKey(constants.PCRPublicKey) + if err != nil { + return nil, err + } + + loadExternal := tpm2.LoadExternal{ + Hierarchy: tpm2.TPMRHOwner, + InPublic: tpm2.New2B(RSAPubKeyTemplate(pubKey.N.BitLen(), pubKey.E, pubKey.N.Bytes())), + } + + loadExternalResponse, err := loadExternal.Execute(t) + if err != nil { + return nil, fmt.Errorf("failed to load external key: %v", err) + } + + defer func() { + flush := tpm2.FlushContext{ + FlushHandle: loadExternalResponse.ObjectHandle, + } + + _, flushErr := flush.Execute(t) + if flushErr != nil { + err = flushErr + } + }() + + pcrSelector, err := CreateSelector([]int{secureboot.UKIPCR}) + if err != nil { + return nil, err + } + + policyDigest, err := PolicyPCRDigest(t, policySess.Handle(), tpm2.TPMLPCRSelection{ + PCRSelections: []tpm2.TPMSPCRSelection{ + { + Hash: tpm2.TPMAlgSHA256, + PCRSelect: pcrSelector, + }, + }, + }) + if err != nil { + return nil, fmt.Errorf("failed to retrieve policy digest: %v", err) + } + + sigJSON, err := ParsePCRSignature() + if err != nil { + return nil, err + } + + pubKeyFingerprint := sha256.Sum256(x509.MarshalPKCS1PublicKey(pubKey)) + + var signature string + // TODO: maybe we should use the highest supported algorithm of the TPM + // fallback to the next one if the signature is not found + for _, bank := range sigJSON.SHA256 { + digest, decodeErr := hex.DecodeString(bank.Pol) + if decodeErr != nil { + return nil, decodeErr + } + + if bytes.Equal(digest, policyDigest.Buffer) { + signature = bank.Sig + + if hex.EncodeToString(pubKeyFingerprint[:]) != bank.PKFP { + return nil, fmt.Errorf("certificate fingerprint does not match") + } + + break + } + } + + if signature == "" { + return nil, fmt.Errorf("signature not found") + } + + signatureDecoded, err := base64.StdEncoding.DecodeString(signature) + if err != nil { + return nil, err + } + + // Verify will only verify the RSA part of the RSA+SHA256 signature, + // hence we need to do the SHA256 part ourselves + policyDigestHash := sha256.Sum256(policyDigest.Buffer) + + verifySignature := tpm2.VerifySignature{ + KeyHandle: loadExternalResponse.ObjectHandle, + Digest: tpm2.TPM2BDigest{ + Buffer: policyDigestHash[:], + }, + Signature: tpm2.TPMTSignature{ + SigAlg: tpm2.TPMAlgRSASSA, + Signature: tpm2.NewTPMUSignature(tpm2.TPMAlgRSASSA, &tpm2.TPMSSignatureRSA{ + Hash: tpm2.TPMAlgSHA256, + Sig: tpm2.TPM2BPublicKeyRSA{ + Buffer: signatureDecoded, + }, + }), + }, + } + + verifySignatureResponse, err := verifySignature.Execute(t) + if err != nil { + return nil, fmt.Errorf("failed to verify signature: %v", err) + } + + policyAuthorize := tpm2.PolicyAuthorize{ + PolicySession: policySess.Handle(), + ApprovedPolicy: *policyDigest, + KeySign: loadExternalResponse.Name, + CheckTicket: verifySignatureResponse.Validation, + } + + if _, err = policyAuthorize.Execute(t); err != nil { + return nil, fmt.Errorf("failed to execute policy authorize: %v", err) + } + + secureBootStatePCRSelector, err := CreateSelector([]int{secureboot.SecureBootStatePCR}) + if err != nil { + return nil, err + } + + secureBootStatePolicyDigest, err := PolicyPCRDigest(t, policySess.Handle(), tpm2.TPMLPCRSelection{ + PCRSelections: []tpm2.TPMSPCRSelection{ + { + Hash: tpm2.TPMAlgSHA256, + PCRSelect: secureBootStatePCRSelector, + }, + }, + }) + + if !bytes.Equal(secureBootStatePolicyDigest.Buffer, sealed.PolicyDigest) { + return nil, fmt.Errorf("sealing policy digest does not match") + } + + unsealOp := tpm2.Unseal{ + ItemHandle: tpm2.AuthHandle{ + Handle: loadResponse.ObjectHandle, + Name: loadResponse.Name, + Auth: policySess, + }, + } + + unsealResponse, err := unsealOp.Execute(t, tpm2.HMAC( + tpm2.TPMAlgSHA256, + 20, + tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub), + tpm2.AESEncryption(128, tpm2.EncryptOut), + tpm2.Bound(loadResponse.ObjectHandle, loadResponse.Name, nil), + )) + if err != nil { + return nil, fmt.Errorf("failed to unseal op: %v", err) + } + + return unsealResponse.OutData.Buffer, nil +} diff --git a/internal/pkg/tpm2/tpm2.go b/internal/pkg/tpm2/tpm2.go deleted file mode 100644 index 2cf6aff65..000000000 --- a/internal/pkg/tpm2/tpm2.go +++ /dev/null @@ -1,697 +0,0 @@ -// This Source Code Form is subject to the terms of the Mozilla Public -// License, v. 2.0. If a copy of the MPL was not distributed with this -// file, You can obtain one at http://mozilla.org/MPL/2.0/. - -// Package tpm2 provides TPM2.0 related functionality helpers. -package tpm2 - -import ( - "bytes" - "crypto" - "crypto/rsa" - "crypto/sha256" - "crypto/x509" - "encoding/base64" - "encoding/binary" - "encoding/hex" - "encoding/json" - "encoding/pem" - "fmt" - "log" - "os" - - "github.com/google/go-tpm/tpm2" - "github.com/google/go-tpm/tpm2/transport" - "github.com/google/go-tpm/tpmutil" - - "github.com/siderolabs/talos/pkg/machinery/constants" -) - -// PCRData is the data structure for PCR signature json. -type PCRData struct { - SHA1 []BankData `json:"sha1,omitempty"` - SHA256 []BankData `json:"sha256,omitempty"` - SHA384 []BankData `json:"sha384,omitempty"` - SHA512 []BankData `json:"sha512,omitempty"` -} - -// BankData constains data for a specific PCR bank. -type BankData struct { - // list of PCR banks - PCRs []int `json:"pcrs"` - // Public key of the TPM - PKFP string `json:"pkfp"` - // Policy digest - Pol string `json:"pol"` - // Signature of the policy digest in base64 - Sig string `json:"sig"` -} - -// SealedResponse is the response from the TPM2.0 Seal operation. -type SealedResponse struct { - SealedBlobPrivate []byte - SealedBlobPublic []byte - KeyName []byte - PolicyDigest []byte -} - -// Seal seals the key using TPM2.0. -func Seal(key []byte) (*SealedResponse, error) { - t, err := transport.OpenTPM() - if err != nil { - return nil, err - } - defer t.Close() // nolint: errcheck - - // fail early if PCR banks are not present or filled with all zeroes or 0xff - if err = validatePCRBanks(t); err != nil { - return nil, err - } - - sealingPolicyDigest, err := calculateSealingPolicyDigest(t) - if err != nil { - return nil, err - } - - primary := tpm2.CreatePrimary{ - PrimaryHandle: tpm2.TPMRHOwner, - InPublic: tpm2.New2B(tpm2.ECCSRKTemplate), - } - - createPrimaryResponse, err := primary.Execute(t) - if err != nil { - return nil, err - } - - defer func() { - flush := tpm2.FlushContext{ - FlushHandle: createPrimaryResponse.ObjectHandle, - } - - _, flushErr := flush.Execute(t) - if flushErr != nil { - err = flushErr - } - }() - - outPub, err := createPrimaryResponse.OutPublic.Contents() - if err != nil { - return nil, err - } - - create := tpm2.Create{ - ParentHandle: tpm2.AuthHandle{ - Handle: createPrimaryResponse.ObjectHandle, - Name: createPrimaryResponse.Name, - Auth: tpm2.HMAC( - tpm2.TPMAlgSHA256, - 20, - tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub), - tpm2.AESEncryption(128, tpm2.EncryptInOut), - ), - }, - InSensitive: tpm2.TPM2BSensitiveCreate{ - Sensitive: &tpm2.TPMSSensitiveCreate{ - Data: tpm2.NewTPMUSensitiveCreate(&tpm2.TPM2BSensitiveData{ - Buffer: key, - }), - }, - }, - InPublic: tpm2.New2B(tpm2.TPMTPublic{ - Type: tpm2.TPMAlgKeyedHash, - NameAlg: tpm2.TPMAlgSHA256, - ObjectAttributes: tpm2.TPMAObject{ - FixedTPM: true, - FixedParent: true, - }, - Parameters: tpm2.NewTPMUPublicParms(tpm2.TPMAlgKeyedHash, &tpm2.TPMSKeyedHashParms{ - Scheme: tpm2.TPMTKeyedHashScheme{ - Scheme: tpm2.TPMAlgNull, - }, - }), - AuthPolicy: tpm2.TPM2BDigest{ - Buffer: sealingPolicyDigest, - }, - }), - } - - createResp, err := create.Execute(t) - if err != nil { - return nil, err - } - - resp := SealedResponse{ - SealedBlobPrivate: tpm2.Marshal(createResp.OutPrivate), - SealedBlobPublic: tpm2.Marshal(createResp.OutPublic), - KeyName: tpm2.Marshal(createPrimaryResponse.Name), - PolicyDigest: sealingPolicyDigest, - } - - return &resp, nil -} - -// Unseal unseals a sealed blob using the TPM -// nolint:gocyclo,cyclop -func Unseal(sealed SealedResponse) ([]byte, error) { - t, err := transport.OpenTPM() - if err != nil { - return nil, err - } - defer t.Close() // nolint: errcheck - - // fail early if PCR banks are not present or filled with all zeroes or 0xff - if err = validatePCRBanks(t); err != nil { - return nil, err - } - - tpmPub, err := tpm2.Unmarshal[tpm2.TPM2BPublic](sealed.SealedBlobPublic) - if err != nil { - return nil, err - } - - tpmPriv, err := tpm2.Unmarshal[tpm2.TPM2BPrivate](sealed.SealedBlobPrivate) - if err != nil { - return nil, err - } - - srk, err := tpm2.Unmarshal[tpm2.TPM2BName](sealed.KeyName) - if err != nil { - return nil, err - } - - // we need to create a primary since we don't persist the SRK - primary := tpm2.CreatePrimary{ - PrimaryHandle: tpm2.TPMRHOwner, - InPublic: tpm2.New2B(tpm2.ECCSRKTemplate), - } - - createPrimaryResponse, err := primary.Execute(t) - if err != nil { - return nil, err - } - - defer func() { - flush := tpm2.FlushContext{ - FlushHandle: createPrimaryResponse.ObjectHandle, - } - - _, flushErr := flush.Execute(t) - if flushErr != nil { - err = flushErr - } - }() - - outPub, err := createPrimaryResponse.OutPublic.Contents() - if err != nil { - return nil, err - } - - if !bytes.Equal(createPrimaryResponse.Name.Buffer, srk.Buffer) { - // this means the srk name does not match, possibly due to a different TPM or tpm was reset - // could also mean the disk was used on a different machine - return nil, fmt.Errorf("srk name does not match") - } - - load := tpm2.Load{ - ParentHandle: tpm2.NamedHandle{ - Handle: createPrimaryResponse.ObjectHandle, - Name: createPrimaryResponse.Name, - }, - InPrivate: *tpmPriv, - InPublic: *tpmPub, - } - - loadResponse, err := load.Execute(t) - if err != nil { - return nil, err - } - - policySess, policyCloseFunc, err := tpm2.PolicySession( - t, - tpm2.TPMAlgSHA256, - 20, - tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub), - ) - if err != nil { - return nil, fmt.Errorf("failed to create policy session: %v", err) - } - - defer policyCloseFunc() // nolint: errcheck - - pubKey, err := parsePCRSigningPubKey() - if err != nil { - return nil, err - } - - loadExternal := tpm2.LoadExternal{ - Hierarchy: tpm2.TPMRHOwner, - InPublic: tpm2.New2B(pubKeyTemplate(pubKey.N.BitLen(), pubKey.E, pubKey.N.Bytes())), - } - - loadExternalResponse, err := loadExternal.Execute(t) - if err != nil { - return nil, fmt.Errorf("failed to load external key: %v", err) - } - - defer func() { - flush := tpm2.FlushContext{ - FlushHandle: loadExternalResponse.ObjectHandle, - } - - _, flushErr := flush.Execute(t) - if flushErr != nil { - err = flushErr - } - }() - - pcrSelector, err := createPCRSelection([]int{constants.UKIMeasuredPCR}) - if err != nil { - return nil, err - } - - policyPCR := tpm2.PolicyPCR{ - PolicySession: policySess.Handle(), - Pcrs: tpm2.TPMLPCRSelection{ - PCRSelections: []tpm2.TPMSPCRSelection{ - { - Hash: tpm2.TPMAlgSHA256, - PCRSelect: pcrSelector, - }, - }, - }, - } - - _, err = policyPCR.Execute(t) - if err != nil { - return nil, fmt.Errorf("failed to execute policy pcr: %v", err) - } - - policyGetDigest := tpm2.PolicyGetDigest{ - PolicySession: policySess.Handle(), - } - - policyGetDigestResp, err := policyGetDigest.Execute(t) - if err != nil { - return nil, fmt.Errorf("failed to execute policy get digest: %v", err) - } - - sigJSON, err := parsePCRSignature() - if err != nil { - return nil, err - } - - pubKeyFingerprint := sha256.Sum256(x509.MarshalPKCS1PublicKey(pubKey)) - - var signature string - // TODO: maybe we should use the highest supported algorithm of the TPM - // fallback to the next one if the signature is not found - for _, bank := range sigJSON.SHA256 { - digest, decodeErr := hex.DecodeString(bank.Pol) - if decodeErr != nil { - return nil, decodeErr - } - - if bytes.Equal(digest, policyGetDigestResp.PolicyDigest.Buffer) { - signature = bank.Sig - - if hex.EncodeToString(pubKeyFingerprint[:]) != bank.PKFP { - return nil, fmt.Errorf("certificate fingerprint does not match") - } - - break - } - } - - if signature == "" { - return nil, fmt.Errorf("signature not found") - } - - signatureDecoded, err := base64.StdEncoding.DecodeString(signature) - if err != nil { - return nil, err - } - - // Verify will only verify the RSA part of the RSA+SHA256 signature, - // hence we need to do the SHA256 part ourselves - policyDigestHash := sha256.Sum256(policyGetDigestResp.PolicyDigest.Buffer) - - verifySignature := tpm2.VerifySignature{ - KeyHandle: loadExternalResponse.ObjectHandle, - Digest: tpm2.TPM2BDigest{ - Buffer: policyDigestHash[:], - }, - Signature: tpm2.TPMTSignature{ - SigAlg: tpm2.TPMAlgRSASSA, - Signature: tpm2.NewTPMUSignature(tpm2.TPMAlgRSASSA, &tpm2.TPMSSignatureRSA{ - Hash: tpm2.TPMAlgSHA256, - Sig: tpm2.TPM2BPublicKeyRSA{ - Buffer: signatureDecoded, - }, - }), - }, - } - - verifySignatureResponse, err := verifySignature.Execute(t) - if err != nil { - return nil, fmt.Errorf("failed to verify signature: %v", err) - } - - policyAuthorize := tpm2.PolicyAuthorize{ - PolicySession: policySess.Handle(), - ApprovedPolicy: policyGetDigestResp.PolicyDigest, - KeySign: loadExternalResponse.Name, - CheckTicket: verifySignatureResponse.Validation, - } - - if _, err = policyAuthorize.Execute(t); err != nil { - return nil, fmt.Errorf("failed to execute policy authorize: %v", err) - } - - // we need to call policyPCR again to update the policy digest - if _, err = policyPCR.Execute(t); err != nil { - return nil, fmt.Errorf("failed to execute policy pcr: %v", err) - } - - policyGetDigestResp, err = policyGetDigest.Execute(t) - if err != nil { - return nil, fmt.Errorf("failed to execute policy get digest: %v", err) - } - - if !bytes.Equal(policyGetDigestResp.PolicyDigest.Buffer, sealed.PolicyDigest) { - return nil, fmt.Errorf("sealing policy digest does not match") - } - - unsealOp := tpm2.Unseal{ - ItemHandle: tpm2.AuthHandle{ - Handle: loadResponse.ObjectHandle, - Name: loadResponse.Name, - Auth: policySess, - }, - } - - unsealResponse, err := unsealOp.Execute(t, tpm2.HMAC( - tpm2.TPMAlgSHA256, - 20, - tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub), - tpm2.AESEncryption(128, tpm2.EncryptOut), - tpm2.Bound(loadResponse.ObjectHandle, loadResponse.Name, nil), - )) - if err != nil { - return nil, fmt.Errorf("failed to unseal op: %v", err) - } - - return unsealResponse.OutData.Buffer, nil -} - -// PCRExtent hashes the input and extends the PCR with the hash. -func PCRExtent(pcr int, data []byte) error { - t, err := transport.OpenTPM() - if err != nil { - if os.IsNotExist(err) { - log.Printf("TPM device is not available, skipping PCR extension") - - return nil - } - - return err - } - - defer t.Close() // nolint: errcheck - - // since we are using SHA256, we can assume that the PCR bank is SHA256 - digest := sha256.Sum256(data) - - pcrHandle := tpm2.PCRExtend{ - PCRHandle: tpm2.AuthHandle{ - Handle: tpm2.TPMHandle(pcr), - Auth: tpm2.PasswordAuth(nil), - }, - Digests: tpm2.TPMLDigestValues{ - Digests: []tpm2.TPMTHA{ - { - HashAlg: tpm2.TPMAlgSHA256, - Digest: digest[:], - }, - }, - }, - } - - if _, err = pcrHandle.Execute(t); err != nil { - return err - } - - return nil -} - -func pubKeyTemplate(bitlen, exponent int, modulus []byte) tpm2.TPMTPublic { - return tpm2.TPMTPublic{ - Type: tpm2.TPMAlgRSA, - NameAlg: tpm2.TPMAlgSHA256, - ObjectAttributes: tpm2.TPMAObject{ - Decrypt: true, - SignEncrypt: true, - UserWithAuth: true, - }, - Parameters: tpm2.NewTPMUPublicParms(tpm2.TPMAlgRSA, &tpm2.TPMSRSAParms{ - Symmetric: tpm2.TPMTSymDefObject{ - Algorithm: tpm2.TPMAlgNull, - Mode: tpm2.NewTPMUSymMode(tpm2.TPMAlgRSA, tpm2.TPMAlgNull), - }, - Scheme: tpm2.TPMTRSAScheme{ - Scheme: tpm2.TPMAlgNull, - Details: tpm2.NewTPMUAsymScheme(tpm2.TPMAlgRSA, &tpm2.TPMSSigSchemeRSASSA{ - HashAlg: tpm2.TPMAlgNull, - }), - }, - KeyBits: tpm2.TPMKeyBits(bitlen), - Exponent: uint32(exponent), - }), - Unique: tpm2.NewTPMUPublicID(tpm2.TPMAlgRSA, &tpm2.TPM2BPublicKeyRSA{ - Buffer: modulus, - }), - } -} - -func calculateSealingPolicyDigest(t transport.TPM) ([]byte, error) { - policyAuthorizationDigest, err := calculatePolicyAuthorizationDigest() - if err != nil { - return nil, err - } - - pcrSelector, err := createPCRSelection([]int{constants.UKIMeasuredPCR}) - if err != nil { - return nil, fmt.Errorf("failed to create PCR selection: %v", err) - } - - pcrValue, err := readPCR(t, constants.UKIMeasuredPCR) - if err != nil { - return nil, err - } - - sealingDigest := calculatePolicyPCR(policyAuthorizationDigest, pcrValue, tpm2.TPMLPCRSelection{ - PCRSelections: []tpm2.TPMSPCRSelection{ - { - Hash: tpm2.TPMAlgSHA256, - PCRSelect: pcrSelector, - }, - }, - }) - - return sealingDigest, nil -} - -func calculatePolicyAuthorizationDigest() ([]byte, error) { - tpm2PubKey, err := parsePCRSigningPubKey() - if err != nil { - return nil, err - } - - publicKeyTemplate := pubKeyTemplate(tpm2PubKey.N.BitLen(), tpm2PubKey.E, tpm2PubKey.N.Bytes()) - - name, err := tpm2.ObjectName(&publicKeyTemplate) - if err != nil { - return nil, fmt.Errorf("failed to calculate name: %v", err) - } - - policyAuthorizeCommand := make([]byte, 4) - binary.BigEndian.PutUint32(policyAuthorizeCommand, uint32(tpm2.TPMCCPolicyAuthorize)) - - // PolicyAuthorize does not use the previous hash value - // start with all zeros - initial := bytes.Repeat([]byte{0x00}, sha256.Size) - - initial = append(initial, policyAuthorizeCommand...) - initial = append(initial, name.Buffer...) - - policyAuthorizeInitialDigest := sha256.Sum256(initial) - - // PolicyAuthorize requires hashing twice - policyAuthorizeDigest := sha256.Sum256(policyAuthorizeInitialDigest[:]) - - return policyAuthorizeDigest[:], nil -} - -func parsePCRSigningPubKey() (*rsa.PublicKey, error) { - pcrSigningPubKey, err := os.ReadFile(constants.PCRPublicKey) - if err != nil { - return nil, fmt.Errorf("failed to read pcr signing public key: %v", err) - } - - block, _ := pem.Decode(pcrSigningPubKey) - if block == nil { - return nil, fmt.Errorf("failed to decode pcr signing public key") - } - - // parse rsa public key - tpm2PubKeyAny, err := x509.ParsePKIXPublicKey(block.Bytes) - if err != nil { - return nil, err - } - - tpm2PubKey, ok := tpm2PubKeyAny.(*rsa.PublicKey) - if !ok { - return nil, fmt.Errorf("failed to cast pcr signing public key to rsa") - } - - return tpm2PubKey, nil -} - -func parsePCRSignature() (*PCRData, error) { - pcrSignature, err := os.ReadFile(constants.PCRSignatureJSON) - if err != nil { - return nil, fmt.Errorf("failed to read pcr signature: %v", err) - } - - pcrData := &PCRData{} - - if err = json.Unmarshal(pcrSignature, pcrData); err != nil { - return nil, fmt.Errorf("failed to unmarshal pcr signature: %v", err) - } - - return pcrData, nil -} - -func calculatePolicyPCR(policyAuthorizeDigest, pcrValue []byte, pcrSelection tpm2.TPMLPCRSelection) []byte { - pcrHash := sha256.Sum256(pcrValue) - - policyPCRCommandValue := make([]byte, 4) - binary.BigEndian.PutUint32(policyPCRCommandValue, uint32(tpm2.TPMCCPolicyPCR)) - - pcrSelectionMarshalled := tpm2.Marshal(pcrSelection) - - pcrToHash := make([]byte, 0) - - pcrToHash = append(pcrToHash, policyAuthorizeDigest...) - pcrToHash = append(pcrToHash, policyPCRCommandValue...) - pcrToHash = append(pcrToHash, pcrSelectionMarshalled...) - pcrToHash = append(pcrToHash, pcrHash[:]...) - - pcrDigest := sha256.Sum256(pcrToHash) - - return pcrDigest[:] -} - -// nolint:gocyclo -func validatePCRBanks(t transport.TPM) error { - pcrValue, err := readPCR(t, constants.UKIMeasuredPCR) - if err != nil { - return fmt.Errorf("failed to read PCR: %v", err) - } - - if bytes.Equal(pcrValue, bytes.Repeat([]byte{0x00}, sha256.Size)) { - return fmt.Errorf("PCR bank %d is populated with zeroes", constants.UKIMeasuredPCR) - } - - if bytes.Equal(pcrValue, bytes.Repeat([]byte{0xFF}, sha256.Size)) { - return fmt.Errorf("PCR bank %d is populated with 0xFF", constants.UKIMeasuredPCR) - } - - caps := tpm2.GetCapability{ - Capability: tpm2.TPMCapPCRs, - Property: 0, - PropertyCount: 1, - } - - capsResp, err := caps.Execute(t) - if err != nil { - return fmt.Errorf("failed to get PCR capabilities: %v", err) - } - - assignedPCRs, err := capsResp.CapabilityData.Data.AssignedPCR() - if err != nil { - return fmt.Errorf("failed to parse assigned PCRs: %v", err) - } - - for _, s := range assignedPCRs.PCRSelections { - h, err := s.Hash.Hash() - if err != nil { - return fmt.Errorf("failed to parse hash algorithm: %v", err) - } - - switch h { //nolint:exhaustive - case crypto.SHA1: - continue - case crypto.SHA256: - // check if 24 banks are available - if len(s.PCRSelect) != 24/8 { - return fmt.Errorf("unexpected number of PCR banks: %d", len(s.PCRSelect)) - } - - // check if all banks are available - if s.PCRSelect[0] != 0xff || s.PCRSelect[1] != 0xff || s.PCRSelect[2] != 0xff { - return fmt.Errorf("unexpected PCR banks: %v", s.PCRSelect) - } - case crypto.SHA384: - continue - case crypto.SHA512: - continue - default: - return fmt.Errorf("unsupported hash algorithm: %s", h.String()) - } - } - - return nil -} - -func readPCR(t transport.TPM, pcr int) ([]byte, error) { - pcrSelector, err := createPCRSelection([]int{pcr}) - if err != nil { - return nil, fmt.Errorf("failed to create PCR selection: %v", err) - } - - pcrRead := tpm2.PCRRead{ - PCRSelectionIn: tpm2.TPMLPCRSelection{ - PCRSelections: []tpm2.TPMSPCRSelection{ - { - Hash: tpm2.TPMAlgSHA256, - PCRSelect: pcrSelector, - }, - }, - }, - } - - pcrValue, err := pcrRead.Execute(t) - if err != nil { - return nil, fmt.Errorf("failed to read PCR: %v", err) - } - - return pcrValue.PCRValues.Digests[0].Buffer, nil -} - -func createPCRSelection(s []int) ([]byte, error) { - const sizeOfPCRSelect = 3 - - PCRs := make(tpmutil.RawBytes, sizeOfPCRSelect) - - for _, n := range s { - if n >= 8*sizeOfPCRSelect { - return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1) - } - - byteNum := n / 8 - bytePos := byte(1 << (n % 8)) - PCRs[byteNum] |= bytePos - } - - return PCRs, nil -} diff --git a/pkg/machinery/constants/constants.go b/pkg/machinery/constants/constants.go index 23e4fcac8..2210f31db 100644 --- a/pkg/machinery/constants/constants.go +++ b/pkg/machinery/constants/constants.go @@ -553,9 +553,6 @@ const ( // https://www.mankier.com/7/systemd-stub#Initrd_Resources PCRPublicKey = SDStubDynamicInitrdPath + "/" + "tpm2-pcr-public-key.pem" - // UKIMeasuredPCR is the PCR index that systemd-boot measures the UKI values into. - UKIMeasuredPCR = 11 - // DefaultCertificateValidityDuration is the default duration for a certificate. DefaultCertificateValidityDuration = x509.DefaultCertificateValidityDuration