Thomas Way b87092ab69
fix: handle secure boot state policy pcr digest error
This does not fix the underlying digest mismatch issue, but does handle the error and should provide
further insight into issues (if present).

Refs: #7828

Signed-off-by: Thomas Way <thomas@6f.io>
Signed-off-by: Andrey Smirnov <andrey.smirnov@siderolabs.com>
2023-10-09 18:24:56 +04:00

269 lines
6.6 KiB
Go

// 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: %w", 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: %w", 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: %w", 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: %w", 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: %w", 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 err != nil {
return nil, fmt.Errorf("failed to calculate policy PCR digest: %w", err)
}
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: %w", err)
}
return unsealResponse.OutData.Buffer, nil
}