talm
269 строк · 6.6 Кб
1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
5// Package tpm2 provides TPM2.0 related functionality helpers.
6package tpm2
7
8import (
9"bytes"
10"crypto/sha256"
11"crypto/x509"
12"encoding/base64"
13"encoding/hex"
14"errors"
15"fmt"
16
17"github.com/google/go-tpm/tpm2"
18"github.com/google/go-tpm/tpm2/transport"
19
20"github.com/aenix-io/talm/internal/pkg/secureboot"
21"github.com/siderolabs/talos/pkg/machinery/constants"
22)
23
24// Unseal unseals a sealed blob using the TPM
25//
26//nolint:gocyclo,cyclop
27func Unseal(sealed SealedResponse) ([]byte, error) {
28t, err := transport.OpenTPM()
29if err != nil {
30return nil, err
31}
32defer t.Close() //nolint:errcheck
33
34// fail early if PCR banks are not present or filled with all zeroes or 0xff
35if err = validatePCRBanks(t); err != nil {
36return nil, err
37}
38
39tpmPub, err := tpm2.Unmarshal[tpm2.TPM2BPublic](sealed.SealedBlobPublic)
40if err != nil {
41return nil, err
42}
43
44tpmPriv, err := tpm2.Unmarshal[tpm2.TPM2BPrivate](sealed.SealedBlobPrivate)
45if err != nil {
46return nil, err
47}
48
49srk, err := tpm2.Unmarshal[tpm2.TPM2BName](sealed.KeyName)
50if err != nil {
51return nil, err
52}
53
54// we need to create a primary since we don't persist the SRK
55primary := tpm2.CreatePrimary{
56PrimaryHandle: tpm2.TPMRHOwner,
57InPublic: tpm2.New2B(tpm2.ECCSRKTemplate),
58}
59
60createPrimaryResponse, err := primary.Execute(t)
61if err != nil {
62return nil, err
63}
64
65defer func() {
66flush := tpm2.FlushContext{
67FlushHandle: createPrimaryResponse.ObjectHandle,
68}
69
70_, flushErr := flush.Execute(t)
71if flushErr != nil {
72err = flushErr
73}
74}()
75
76outPub, err := createPrimaryResponse.OutPublic.Contents()
77if err != nil {
78return nil, err
79}
80
81if !bytes.Equal(createPrimaryResponse.Name.Buffer, srk.Buffer) {
82// this means the srk name does not match, possibly due to a different TPM or tpm was reset
83// could also mean the disk was used on a different machine
84return nil, errors.New("srk name does not match")
85}
86
87load := tpm2.Load{
88ParentHandle: tpm2.NamedHandle{
89Handle: createPrimaryResponse.ObjectHandle,
90Name: createPrimaryResponse.Name,
91},
92InPrivate: *tpmPriv,
93InPublic: *tpmPub,
94}
95
96loadResponse, err := load.Execute(t)
97if err != nil {
98return nil, err
99}
100
101policySess, policyCloseFunc, err := tpm2.PolicySession(
102t,
103tpm2.TPMAlgSHA256,
10420,
105tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub),
106)
107if err != nil {
108return nil, fmt.Errorf("failed to create policy session: %w", err)
109}
110
111defer policyCloseFunc() //nolint:errcheck
112
113pubKey, err := ParsePCRSigningPubKey(constants.PCRPublicKey)
114if err != nil {
115return nil, err
116}
117
118loadExternal := tpm2.LoadExternal{
119Hierarchy: tpm2.TPMRHOwner,
120InPublic: tpm2.New2B(RSAPubKeyTemplate(pubKey.N.BitLen(), pubKey.E, pubKey.N.Bytes())),
121}
122
123loadExternalResponse, err := loadExternal.Execute(t)
124if err != nil {
125return nil, fmt.Errorf("failed to load external key: %w", err)
126}
127
128defer func() {
129flush := tpm2.FlushContext{
130FlushHandle: loadExternalResponse.ObjectHandle,
131}
132
133_, flushErr := flush.Execute(t)
134if flushErr != nil {
135err = flushErr
136}
137}()
138
139pcrSelector, err := CreateSelector([]int{secureboot.UKIPCR})
140if err != nil {
141return nil, err
142}
143
144policyDigest, err := PolicyPCRDigest(t, policySess.Handle(), tpm2.TPMLPCRSelection{
145PCRSelections: []tpm2.TPMSPCRSelection{
146{
147Hash: tpm2.TPMAlgSHA256,
148PCRSelect: pcrSelector,
149},
150},
151})
152if err != nil {
153return nil, fmt.Errorf("failed to retrieve policy digest: %w", err)
154}
155
156sigJSON, err := ParsePCRSignature()
157if err != nil {
158return nil, err
159}
160
161pubKeyFingerprint := sha256.Sum256(x509.MarshalPKCS1PublicKey(pubKey))
162
163var signature string
164// TODO: maybe we should use the highest supported algorithm of the TPM
165// fallback to the next one if the signature is not found
166for _, bank := range sigJSON.SHA256 {
167digest, decodeErr := hex.DecodeString(bank.Pol)
168if decodeErr != nil {
169return nil, decodeErr
170}
171
172if bytes.Equal(digest, policyDigest.Buffer) {
173signature = bank.Sig
174
175if hex.EncodeToString(pubKeyFingerprint[:]) != bank.PKFP {
176return nil, errors.New("certificate fingerprint does not match")
177}
178
179break
180}
181}
182
183if signature == "" {
184return nil, errors.New("signature not found")
185}
186
187signatureDecoded, err := base64.StdEncoding.DecodeString(signature)
188if err != nil {
189return nil, err
190}
191
192// Verify will only verify the RSA part of the RSA+SHA256 signature,
193// hence we need to do the SHA256 part ourselves
194policyDigestHash := sha256.Sum256(policyDigest.Buffer)
195
196verifySignature := tpm2.VerifySignature{
197KeyHandle: loadExternalResponse.ObjectHandle,
198Digest: tpm2.TPM2BDigest{
199Buffer: policyDigestHash[:],
200},
201Signature: tpm2.TPMTSignature{
202SigAlg: tpm2.TPMAlgRSASSA,
203Signature: tpm2.NewTPMUSignature(tpm2.TPMAlgRSASSA, &tpm2.TPMSSignatureRSA{
204Hash: tpm2.TPMAlgSHA256,
205Sig: tpm2.TPM2BPublicKeyRSA{
206Buffer: signatureDecoded,
207},
208}),
209},
210}
211
212verifySignatureResponse, err := verifySignature.Execute(t)
213if err != nil {
214return nil, fmt.Errorf("failed to verify signature: %w", err)
215}
216
217policyAuthorize := tpm2.PolicyAuthorize{
218PolicySession: policySess.Handle(),
219ApprovedPolicy: *policyDigest,
220KeySign: loadExternalResponse.Name,
221CheckTicket: verifySignatureResponse.Validation,
222}
223
224if _, err = policyAuthorize.Execute(t); err != nil {
225return nil, fmt.Errorf("failed to execute policy authorize: %w", err)
226}
227
228secureBootStatePCRSelector, err := CreateSelector([]int{secureboot.SecureBootStatePCR})
229if err != nil {
230return nil, err
231}
232
233secureBootStatePolicyDigest, err := PolicyPCRDigest(t, policySess.Handle(), tpm2.TPMLPCRSelection{
234PCRSelections: []tpm2.TPMSPCRSelection{
235{
236Hash: tpm2.TPMAlgSHA256,
237PCRSelect: secureBootStatePCRSelector,
238},
239},
240})
241if err != nil {
242return nil, fmt.Errorf("failed to calculate policy PCR digest: %w", err)
243}
244
245if !bytes.Equal(secureBootStatePolicyDigest.Buffer, sealed.PolicyDigest) {
246return nil, errors.New("sealing policy digest does not match")
247}
248
249unsealOp := tpm2.Unseal{
250ItemHandle: tpm2.AuthHandle{
251Handle: loadResponse.ObjectHandle,
252Name: loadResponse.Name,
253Auth: policySess,
254},
255}
256
257unsealResponse, err := unsealOp.Execute(t, tpm2.HMAC(
258tpm2.TPMAlgSHA256,
25920,
260tpm2.Salted(createPrimaryResponse.ObjectHandle, *outPub),
261tpm2.AESEncryption(128, tpm2.EncryptOut),
262tpm2.Bound(loadResponse.ObjectHandle, loadResponse.Name, nil),
263))
264if err != nil {
265return nil, fmt.Errorf("failed to unseal op: %w", err)
266}
267
268return unsealResponse.OutData.Buffer, nil
269}
270