-
Notifications
You must be signed in to change notification settings - Fork 72
/
Copy pathverify.go
361 lines (320 loc) · 13.3 KB
/
verify.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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
package server
import (
"crypto"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"github.com/google/go-eventlog/proto/state"
"github.com/google/go-eventlog/register"
"github.com/google/go-tpm-tools/internal"
pb "github.com/google/go-tpm-tools/proto/attest"
tpmpb "github.com/google/go-tpm-tools/proto/tpm"
"github.com/google/go-tpm/legacy/tpm2"
"google.golang.org/protobuf/proto"
)
// We conditinally support SHA-1 for PCR hashes, but at the lowest priority.
var pcrHashAlgs = append(internal.SignatureHashAlgs, tpm2.AlgSHA1)
var oidExtensionSubjectAltName = []int{2, 5, 29, 17}
var cloudComputeInstanceIdentifierOID asn1.ObjectIdentifier = []int{1, 3, 6, 1, 4, 1, 11129, 2, 1, 21}
// VerifyOpts allows for customizing the functionality of VerifyAttestation.
type VerifyOpts struct {
// The nonce used when calling client.Attest
Nonce []byte
// Trusted public keys that can be used to directly verify the key used for
// attestation. This option should be used if you already know the AK, as
// it provides the highest level of assurance.
TrustedAKs []crypto.PublicKey
// Allow using SHA-1 PCRs to verify attestations. This defaults to false
// because SHA-1 is a weak hash algorithm with known collision attacks.
// However, setting this to true may be necessary if the client only
// supports the legacy event log format. This is the case on older Linux
// distributions (such as Debian 10). Note that this will NOT allow
// SHA-1 signatures to be used, just SHA-1 PCRs.
AllowSHA1 bool
// A collection of trusted root CAs that are used to sign AK certificates.
// The TrustedAKs are used first, followed by TrustRootCerts and
// IntermediateCerts.
// Adding a specific TPM manufacturer's root and intermediate CAs means all
// TPMs signed by that CA will be trusted.
// To trust the MachineState's GCE instance_info, the caller MUST use
// authentic Google-signed certificates provided in server/ca-certs
// OR fetched via
// https://privateca-content-62d71773-0000-21da-852e-f4f5e80d7778.storage.googleapis.com/032bf9d39db4fa06aade/ca.crt
// https://pki.goog/cloud_integrity/tpm_ek_root_1.crt.
TrustedRootCerts []*x509.Certificate
IntermediateCerts []*x509.Certificate
// Which bootloader the instance uses. Pick UNSUPPORTED to skip this
// parsing or for unsupported bootloaders (e.g., systemd).
Loader Bootloader
// TEEOpts allows customizing the functionality of VerifyTEEAttestation.
// Its type can be *VerifySnpOpts if the TEEAttestation is a SevSnpAttestation
// or can be *VerifyTdxOpts if the TEEAttestation is a TdxAttestation
// If nil, uses Nonce for ReportData and the TEE's verification library's
// embedded root certs for its roots of trust.
//
// Deprecated: go-tpm-tools no longer verifies SNP or TDX attestation.
// Please use go-sev-guest and go-tdx-guest.
TEEOpts interface{}
}
// Bootloader refers to the second-stage bootloader that loads and transfers
// execution to the OS kernel.
type Bootloader int
const (
// UnsupportedLoader refers to a second-stage bootloader that is of an
// unsupported type. VerifyAttestation will not parse the PCClient Event Log
// for bootloader events.
UnsupportedLoader Bootloader = iota
// GRUB (https://www.gnu.org/software/grub/).
GRUB
)
// TODO: Change int64 fields to uint64 when compatible with ASN1 parsing.
type gceSecurityProperties struct {
SecurityVersion int64 `asn1:"explicit,tag:0,optional"`
IsProduction bool `asn1:"explicit,tag:1,optional"`
}
type gceInstanceInfo struct {
Zone string `asn1:"utf8"`
ProjectNumber int64
ProjectID string `asn1:"utf8"`
InstanceID int64
InstanceName string `asn1:"utf8"`
SecurityProperties gceSecurityProperties `asn1:"explicit,optional"`
}
// VerifyAttestation performs the following checks on an Attestation:
// - the AK used to generate the attestation is trusted (based on VerifyOpts)
// - the provided signature is generated by the trusted AK public key
// - the signature signs the provided quote data
// - the quote data starts with TPM_GENERATED_VALUE
// - the quote data is a valid TPMS_QUOTE_INFO
// - the quote data was taken over the provided PCRs
// - the provided PCR values match the quote data internal digest
// - the provided opts.Nonce matches that in the quote data
// - the provided eventlog matches the provided PCR values
//
// After this, the eventlog is parsed and the corresponding MachineState is
// returned. This design prevents unverified MachineStates from being used.
func VerifyAttestation(attestation *pb.Attestation, opts VerifyOpts) (*pb.MachineState, error) {
if err := validateOpts(opts); err != nil {
return nil, fmt.Errorf("bad options: %w", err)
}
machineState, akPubKey, err := validateAK(attestation, opts)
if err != nil {
return nil, fmt.Errorf("failed to parse and validate AK: %w", err)
}
// Attempt to replay the log against our PCRs in order of hash preference
var lastErr error
for _, quote := range supportedQuotes(attestation.GetQuotes()) {
// Verify the Quote
if err := internal.VerifyQuote(quote, akPubKey, opts.Nonce); err != nil {
lastErr = fmt.Errorf("failed to verify quote: %w", err)
continue
}
// Parse event logs and replay the events against the provided PCRs
pcrs := quote.GetPcrs()
tpmMachineState, err := parseMachineStateFromTPM(attestation, pcrs, opts)
if err != nil {
lastErr = fmt.Errorf("failed to parse machine state from TCG event log: %w", err)
continue
}
pcrBank := register.PCRBank{TCGHashAlgo: state.HashAlgo(pcrs.Hash)}
digestAlg, err := pcrBank.TCGHashAlgo.CryptoHash()
if err != nil {
return nil, fmt.Errorf("invalid digest algorithm")
}
for pcrIndex, digest := range pcrs.GetPcrs() {
pcrBank.PCRs = append(pcrBank.PCRs, register.PCR{
Index: int(pcrIndex),
Digest: digest,
DigestAlg: digestAlg})
}
celState, err := ParseCosCELPCR(attestation.GetCanonicalEventLog(), pcrBank)
if err != nil {
lastErr = fmt.Errorf("failed to validate the Canonical event log: %w", err)
continue
}
machineState.Cos = celState
// Verify the PCR hash algorithm. We have this check here (instead of at
// the start of the loop) so that the user gets a "SHA-1 not supported"
// error only if allowing SHA-1 support would actually allow the log
// to be verified. This makes debugging failed verifications easier.
if !opts.AllowSHA1 && tpm2.Algorithm(pcrs.GetHash()) == tpm2.AlgSHA1 {
lastErr = fmt.Errorf("SHA-1 is not allowed for verification (set VerifyOpts.AllowSHA1 to true to allow)")
continue
}
proto.Merge(machineState, tpmMachineState)
return machineState, nil
}
if lastErr != nil {
return nil, lastErr
}
return nil, fmt.Errorf("attestation does not contain a supported quote")
}
// validateAK validates AK cert in the attestation, and returns AK cert (if exists) and public key.
// It also pulls out the GCE Instance Info if it exists.
func validateAK(attestation *pb.Attestation, opts VerifyOpts) (*pb.MachineState, crypto.PublicKey, error) {
if len(attestation.GetAkCert()) == 0 || len(opts.TrustedRootCerts) == 0 {
// If the AK Cert is not in the attestation, use the AK Public Area.
akPubArea, err := tpm2.DecodePublic(attestation.GetAkPub())
if err != nil {
return nil, nil, fmt.Errorf("failed to decode AK public area: %w", err)
}
akPubKey, err := akPubArea.Key()
if err != nil {
return nil, nil, fmt.Errorf("failed to get AK public key: %w", err)
}
if err := validateAKPub(akPubKey, opts); err != nil {
return nil, nil, fmt.Errorf("failed to validate AK public key: %w", err)
}
return &pb.MachineState{}, akPubKey, nil
}
// If AK Cert is presented, ignore the AK Public Area.
akCert, err := x509.ParseCertificate(attestation.GetAkCert())
if err != nil {
return nil, nil, fmt.Errorf("failed to parse AK certificate: %w", err)
}
// Use intermediate certs from the attestation if they exist.
certs, err := parseCerts(attestation.IntermediateCerts)
if err != nil {
return nil, nil, fmt.Errorf("attestation intermediates: %w", err)
}
opts.IntermediateCerts = append(opts.IntermediateCerts, certs...)
if err := VerifyAKCert(akCert, opts.TrustedRootCerts, opts.IntermediateCerts); err != nil {
return nil, nil, fmt.Errorf("failed to validate AK certificate: %w", err)
}
instanceInfo, err := getInstanceInfoFromExtensions(akCert.Extensions)
if err != nil {
return nil, nil, fmt.Errorf("error getting instance info: %v", err)
}
return &pb.MachineState{Platform: &pb.PlatformState{InstanceInfo: instanceInfo}}, akCert.PublicKey, nil
}
// GetGCEInstanceInfo takes a GCE-issued x509 EK/AK certificate and tries to
// extract its GCE instance information. It returns an error if the cert is nil
// or malformed, but it does not return an error if the cert does not contain
// the GCE Instance OID.
// The caller must first `ValidateAKCert` using a GCE EK Certificate root CA.
func GetGCEInstanceInfo(cert *x509.Certificate) (*pb.GCEInstanceInfo, error) {
if cert == nil {
return nil, errors.New("cannot extract GCEInstanceInfo from a nil cert")
}
return getInstanceInfoFromExtensions(cert.Extensions)
}
func getInstanceInfoFromExtensions(extensions []pkix.Extension) (*pb.GCEInstanceInfo, error) {
var rawInfo []byte
for _, ext := range extensions {
if ext.Id.Equal(cloudComputeInstanceIdentifierOID) {
rawInfo = ext.Value
break
}
}
// If GCE Instance Info extension is not found.
if len(rawInfo) == 0 {
return nil, nil
}
info := gceInstanceInfo{}
if _, err := asn1.Unmarshal(rawInfo, &info); err != nil {
return nil, fmt.Errorf("failed to parse GCE Instance Information Extension: %w", err)
}
// TODO: Remove when fields are changed to uint64.
if info.ProjectNumber < 0 || info.InstanceID < 0 || info.SecurityProperties.SecurityVersion < 0 {
return nil, fmt.Errorf("negative integer fields found in GCE Instance Information Extension")
}
// Check production.
if !info.SecurityProperties.IsProduction {
return nil, nil
}
return &pb.GCEInstanceInfo{
Zone: info.Zone,
ProjectId: info.ProjectID,
ProjectNumber: uint64(info.ProjectNumber),
InstanceName: info.InstanceName,
InstanceId: uint64(info.InstanceID),
}, nil
}
// Check that we are passing in a valid VerifyOpts structure
func validateOpts(opts VerifyOpts) error {
checkPub := len(opts.TrustedAKs) > 0
checkCert := len(opts.TrustedRootCerts) > 0
if !checkPub && !checkCert {
return fmt.Errorf("no trust mechanism provided, either use TrustedAKs or TrustedRootCerts")
}
if checkPub && checkCert {
return fmt.Errorf("multiple trust mechanisms provided, only use one of TrustedAKs or TrustedRootCerts")
}
return nil
}
func validateAKPub(ak crypto.PublicKey, opts VerifyOpts) error {
for _, trusted := range opts.TrustedAKs {
if internal.PubKeysEqual(ak, trusted) {
return nil
}
}
return fmt.Errorf("key not trusted")
}
// VerifyAKCert checks a given Attestation Key certificate against the provided
// root and intermediate CAs.
func VerifyAKCert(akCert *x509.Certificate, trustedRootCerts []*x509.Certificate, intermediateCerts []*x509.Certificate) error {
if akCert == nil {
return errors.New("failed to validate AK Cert: received nil cert")
}
if len(trustedRootCerts) == 0 {
return errors.New("failed to validate AK Cert: received no trusted root certs")
}
// We manually handle the SAN extension because x509 marks it unhandled if
// SAN does not parse any of DNSNames, EmailAddresses, IPAddresses, or URIs.
// https://cs.opensource.google/go/go/+/master:src/crypto/x509/parser.go;l=668-678
var exts []asn1.ObjectIdentifier
for _, ext := range akCert.UnhandledCriticalExtensions {
if ext.Equal(oidExtensionSubjectAltName) {
continue
}
exts = append(exts, ext)
}
akCert.UnhandledCriticalExtensions = exts
x509Opts := x509.VerifyOptions{
Roots: makePool(trustedRootCerts),
Intermediates: makePool(intermediateCerts),
// The default key usage (ExtKeyUsageServerAuth) is not appropriate for
// an Attestation Key: ExtKeyUsage of
// - https://oidref.com/2.23.133.8.1
// - https://oidref.com/2.23.133.8.3
// https://pkg.go.dev/crypto/x509#VerifyOptions
KeyUsages: []x509.ExtKeyUsage{x509.ExtKeyUsage(x509.ExtKeyUsageAny)},
}
if _, err := akCert.Verify(x509Opts); err != nil {
return fmt.Errorf("certificate did not chain to a trusted root: %v", err)
}
return nil
}
// Retrieve the supported quotes in order of hash preference.
func supportedQuotes(quotes []*tpmpb.Quote) []*tpmpb.Quote {
out := make([]*tpmpb.Quote, 0, len(quotes))
for _, alg := range pcrHashAlgs {
for _, quote := range quotes {
if tpm2.Algorithm(quote.GetPcrs().GetHash()) == alg {
out = append(out, quote)
break
}
}
}
return out
}
func makePool(certs []*x509.Certificate) *x509.CertPool {
pool := x509.NewCertPool()
for _, cert := range certs {
pool.AddCert(cert)
}
return pool
}
// parseMachineStateFromTPM is a wrapper function around `parsePCClientEventLog` method to:
// 1. parse partial machine state from TPM TCG event logs.
// 2. verify GceTechnology since the GCE Technology event is directly related to the TPM.
// 3. populate the machineState TeeAttestatation field with the verified TDX/SNP attestation data.
func parseMachineStateFromTPM(attestation *pb.Attestation, pcrs *tpmpb.PCRs, opts VerifyOpts) (*pb.MachineState, error) {
ms, err := parsePCClientEventLog(attestation.GetEventLog(), pcrs, opts.Loader)
if err != nil {
return nil, fmt.Errorf("failed to validate the PCClient event log: %w", err)
}
return ms, nil
}