talm
194 строки · 4.7 Кб
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"fmt"
12"log"
13"os"
14"strings"
15
16"github.com/google/go-tpm/tpm2"
17"github.com/google/go-tpm/tpm2/transport"
18
19"github.com/aenix-io/talm/internal/pkg/secureboot"
20)
21
22// CreateSelector converts PCR numbers into a bitmask.
23func CreateSelector(pcrs []int) ([]byte, error) {
24const sizeOfPCRSelect = 3
25
26mask := make([]byte, sizeOfPCRSelect)
27
28for _, n := range pcrs {
29if n >= 8*sizeOfPCRSelect {
30return nil, fmt.Errorf("PCR index %d is out of range (exceeds maximum value %d)", n, 8*sizeOfPCRSelect-1)
31}
32
33mask[n>>3] |= 1 << (n & 0x7)
34}
35
36return mask, nil
37}
38
39// ReadPCR reads the value of a single PCR.
40func ReadPCR(t transport.TPM, pcr int) ([]byte, error) {
41pcrSelector, err := CreateSelector([]int{pcr})
42if err != nil {
43return nil, fmt.Errorf("failed to create PCR selection: %w", err)
44}
45
46pcrRead := tpm2.PCRRead{
47PCRSelectionIn: tpm2.TPMLPCRSelection{
48PCRSelections: []tpm2.TPMSPCRSelection{
49{
50Hash: tpm2.TPMAlgSHA256,
51PCRSelect: pcrSelector,
52},
53},
54},
55}
56
57pcrValue, err := pcrRead.Execute(t)
58if err != nil {
59return nil, fmt.Errorf("failed to read PCR: %w", err)
60}
61
62return pcrValue.PCRValues.Digests[0].Buffer, nil
63}
64
65// PCRExtent hashes the input and extends the PCR with the hash.
66func PCRExtent(pcr int, data []byte) error {
67t, err := transport.OpenTPM()
68if err != nil {
69// if the TPM is not available or not a TPM 2.0, we can skip the PCR extension
70if os.IsNotExist(err) || strings.Contains(err.Error(), "device is not a TPM 2.0") {
71log.Printf("TPM device is not available, skipping PCR extension")
72
73return nil
74}
75
76return err
77}
78
79defer t.Close() //nolint:errcheck
80
81// since we are using SHA256, we can assume that the PCR bank is SHA256
82digest := sha256.Sum256(data)
83
84pcrHandle := tpm2.PCRExtend{
85PCRHandle: tpm2.AuthHandle{
86Handle: tpm2.TPMHandle(pcr),
87Auth: tpm2.PasswordAuth(nil),
88},
89Digests: tpm2.TPMLDigestValues{
90Digests: []tpm2.TPMTHA{
91{
92HashAlg: tpm2.TPMAlgSHA256,
93Digest: digest[:],
94},
95},
96},
97}
98
99if _, err = pcrHandle.Execute(t); err != nil {
100return err
101}
102
103return nil
104}
105
106// PolicyPCRDigest executes policyPCR and returns the digest.
107func PolicyPCRDigest(t transport.TPM, policyHandle tpm2.TPMHandle, pcrSelection tpm2.TPMLPCRSelection) (*tpm2.TPM2BDigest, error) {
108policyPCR := tpm2.PolicyPCR{
109PolicySession: policyHandle,
110Pcrs: pcrSelection,
111}
112
113if _, err := policyPCR.Execute(t); err != nil {
114return nil, fmt.Errorf("failed to execute policyPCR: %w", err)
115}
116
117policyGetDigest := tpm2.PolicyGetDigest{
118PolicySession: policyHandle,
119}
120
121policyGetDigestResponse, err := policyGetDigest.Execute(t)
122if err != nil {
123return nil, fmt.Errorf("failed to get policy digest: %w", err)
124}
125
126return &policyGetDigestResponse.PolicyDigest, nil
127}
128
129//nolint:gocyclo
130func validatePCRBanks(t transport.TPM) error {
131pcrValue, err := ReadPCR(t, secureboot.UKIPCR)
132if err != nil {
133return fmt.Errorf("failed to read PCR: %w", err)
134}
135
136if err = validatePCRNotZeroAndNotFilled(pcrValue, secureboot.UKIPCR); err != nil {
137return err
138}
139
140pcrValue, err = ReadPCR(t, secureboot.SecureBootStatePCR)
141if err != nil {
142return fmt.Errorf("failed to read PCR: %w", err)
143}
144
145if err = validatePCRNotZeroAndNotFilled(pcrValue, secureboot.SecureBootStatePCR); err != nil {
146return err
147}
148
149caps := tpm2.GetCapability{
150Capability: tpm2.TPMCapPCRs,
151Property: 0,
152PropertyCount: 1,
153}
154
155capsResp, err := caps.Execute(t)
156if err != nil {
157return fmt.Errorf("failed to get PCR capabilities: %w", err)
158}
159
160assignedPCRs, err := capsResp.CapabilityData.Data.AssignedPCR()
161if err != nil {
162return fmt.Errorf("failed to parse assigned PCRs: %w", err)
163}
164
165for _, s := range assignedPCRs.PCRSelections {
166if s.Hash != tpm2.TPMAlgSHA256 {
167continue
168}
169
170// check if 24 banks are available
171if len(s.PCRSelect) != 24/8 {
172return fmt.Errorf("unexpected number of PCR banks: %d", len(s.PCRSelect))
173}
174
175// check if all banks are available
176if s.PCRSelect[0] != 0xff || s.PCRSelect[1] != 0xff || s.PCRSelect[2] != 0xff {
177return fmt.Errorf("unexpected PCR banks: %v", s.PCRSelect)
178}
179}
180
181return nil
182}
183
184func validatePCRNotZeroAndNotFilled(pcrValue []byte, pcr int) error {
185if bytes.Equal(pcrValue, bytes.Repeat([]byte{0x00}, sha256.Size)) {
186return fmt.Errorf("PCR bank %d is populated with all zeroes", pcr)
187}
188
189if bytes.Equal(pcrValue, bytes.Repeat([]byte{0xFF}, sha256.Size)) {
190return fmt.Errorf("PCR bank %d is populated with all 0xFF", pcr)
191}
192
193return nil
194}
195