Dmitriy Matrenichev fa3b933705
chore: replace fmt.Errorf with errors.New where possible
This time use `eg` from `x/tools` repo tool to do this.

Signed-off-by: Dmitriy Matrenichev <dmitry.matrenichev@siderolabs.com>
2024-02-14 17:39:30 +03:00

270 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"
"errors"
"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, errors.New("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, errors.New("certificate fingerprint does not match")
}
break
}
}
if signature == "" {
return nil, errors.New("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, errors.New("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
}