-
Notifications
You must be signed in to change notification settings - Fork 71
/
pcr.go
166 lines (145 loc) · 4.48 KB
/
pcr.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
package client
import (
"crypto"
"fmt"
"io"
"math"
pb "github.com/google/go-tpm-tools/proto/tpm"
"github.com/google/go-tpm/tpm2"
)
// NumPCRs is set to the spec minimum of 24, as that's all go-tpm supports.
const NumPCRs = 24
// We hard-code SHA256 as the policy session hash algorithms. Note that this
// differs from the PCR hash algorithm (which selects the bank of PCRs to use)
// and the Public area Name algorithm. We also chose this for compatibility with
// github.com/google/go-tpm/tpm2, as it hardcodes the nameAlg as SHA256 in
// several places. Two constants are used to avoid repeated conversions.
const (
SessionHashAlg = crypto.SHA256
SessionHashAlgTpm = tpm2.AlgSHA256
)
// CertifyHashAlgTpm is the hard-coded algorithm used in certify PCRs.
const CertifyHashAlgTpm = tpm2.AlgSHA256
func min(a, b int) int {
if a < b {
return a
}
return b
}
// Get a list of selections corresponding to the TPM's implemented PCRs
func implementedPCRs(rw io.ReadWriter) ([]tpm2.PCRSelection, error) {
caps, moreData, err := tpm2.GetCapability(rw, tpm2.CapabilityPCRs, math.MaxUint32, 0)
if err != nil {
return nil, fmt.Errorf("listing implemented PCR banks: %w", err)
}
if moreData {
return nil, fmt.Errorf("extra data from GetCapability")
}
sels := make([]tpm2.PCRSelection, len(caps))
for i, cap := range caps {
sel, ok := cap.(tpm2.PCRSelection)
if !ok {
return nil, fmt.Errorf("unexpected data from GetCapability")
}
sels[i] = sel
}
return sels, nil
}
// ReadPCRs fetches all the PCR values specified in sel, making multiple calls
// to the TPM if necessary.
func ReadPCRs(rw io.ReadWriter, sel tpm2.PCRSelection) (*pb.PCRs, error) {
pl := pb.PCRs{
Hash: pb.HashAlgo(sel.Hash),
Pcrs: map[uint32][]byte{},
}
for i := 0; i < len(sel.PCRs); i += 8 {
end := min(i+8, len(sel.PCRs))
pcrSel := tpm2.PCRSelection{
Hash: sel.Hash,
PCRs: sel.PCRs[i:end],
}
pcrMap, err := tpm2.ReadPCRs(rw, pcrSel)
if err != nil {
return nil, err
}
for pcr, val := range pcrMap {
pl.Pcrs[uint32(pcr)] = val
}
}
return &pl, nil
}
// ReadAllPCRs fetches all the PCR values from all implemented PCR banks.
func ReadAllPCRs(rw io.ReadWriter) ([]*pb.PCRs, error) {
sels, err := implementedPCRs(rw)
if err != nil {
return nil, err
}
allPcrs := make([]*pb.PCRs, len(sels))
for i, sel := range sels {
allPcrs[i], err = ReadPCRs(rw, sel)
if err != nil {
return nil, fmt.Errorf("reading bank %x PCRs: %w", sel.Hash, err)
}
}
return allPcrs, nil
}
// SealOpts specifies the PCR values that should be used for Seal().
type SealOpts struct {
// Current seals data to the current specified PCR selection.
Current tpm2.PCRSelection
// Target predictively seals data to the given specified PCR values.
Target *pb.PCRs
}
// UnsealOpts specifies the options that should be used for Unseal().
// Currently, it specifies the PCRs that need to pass certification in order to
// successfully unseal.
// CertifyHashAlgTpm is the hard-coded algorithm that must be used with
// UnsealOpts.
type UnsealOpts struct {
// CertifyCurrent certifies that a selection of current PCRs have the same
// value when sealing.
CertifyCurrent tpm2.PCRSelection
// CertifyExpected certifies that the TPM had a specific set of PCR values when sealing.
CertifyExpected *pb.PCRs
}
// FullPcrSel will return a full PCR selection based on the total PCR number
// of the TPM with the given hash algo.
func FullPcrSel(hash tpm2.Algorithm) tpm2.PCRSelection {
sel := tpm2.PCRSelection{Hash: hash}
for i := 0; i < NumPCRs; i++ {
sel.PCRs = append(sel.PCRs, int(i))
}
return sel
}
func mergePCRSelAndProto(rw io.ReadWriter, sel tpm2.PCRSelection, proto *pb.PCRs) (*pb.PCRs, error) {
if proto == nil || len(proto.GetPcrs()) == 0 {
return ReadPCRs(rw, sel)
}
if len(sel.PCRs) == 0 {
return proto, nil
}
if sel.Hash != tpm2.Algorithm(proto.Hash) {
return nil, fmt.Errorf("current hash (%v) differs from target hash (%v)",
sel.Hash, tpm2.Algorithm(proto.Hash))
}
// At this point, both sel and proto are non-empty.
// Verify no overlap in sel and proto PCR indexes.
overlap := make([]int, 0)
targetMap := proto.GetPcrs()
for _, pcrVal := range sel.PCRs {
if _, found := targetMap[uint32(pcrVal)]; found {
overlap = append(overlap, pcrVal)
}
}
if len(overlap) != 0 {
return nil, fmt.Errorf("found PCR overlap: %v", overlap)
}
currentPcrs, err := ReadPCRs(rw, sel)
if err != nil {
return nil, err
}
for pcr, val := range proto.GetPcrs() {
currentPcrs.Pcrs[pcr] = val
}
return currentPcrs, nil
}