File: client.go

package info (click to toggle)
golang-github-google-go-sev-guest 0.13.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 820 kB
  • sloc: asm: 9; makefile: 3
file content (336 lines) | stat: -rw-r--r-- 11,437 bytes parent folder | download
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
// Copyright 2022 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package client

import (
	"errors"
	"flag"
	"fmt"

	"github.com/google/go-sev-guest/abi"
	labi "github.com/google/go-sev-guest/client/linuxabi"
	pb "github.com/google/go-sev-guest/proto/sevsnp"
)

var sevGuestPath = flag.String("sev_guest_device_path", "default",
	"Path to SEV guest device. If \"default\", uses platform default or a fake if testing.")

// Device encapsulates the possible commands to the AMD SEV guest device.
type Device interface {
	// Open prepares the Device from the given path.
	Open(path string) error
	// Close releases the device resource.
	Close() error
	// Ioctl performs the given command with the given argument.
	Ioctl(command uintptr, argument any) (uintptr, error)
	// Product returns AMD SEV-related CPU information of the calling CPU.
	Product() *pb.SevProduct
}

// LeveledQuoteProvider encapsulates calls to collect an extended attestation report at a given
// privilege level.
type LeveledQuoteProvider interface {
	// IsSupported returns whether the kernel supports this implementation.
	IsSupported() bool
	// GetRawQuote returns a raw report with the given privilege level.
	GetRawQuoteAtLevel(reportData [64]byte, vmpl uint) ([]uint8, error)
	// Product returns AMD SEV-related CPU information of the calling CPU.
	//
	// Deprecated: Use abi.ExtraPlatformInfoGUID in raw quote certificate table.
	Product() *pb.SevProduct
}

// QuoteProvider encapsulates calls to collect an extended attestation report.
type QuoteProvider interface {
	// IsSupported returns whether the kernel supports this implementation.
	IsSupported() bool
	// GetRawQuote returns a raw report with the default privilege level.
	GetRawQuote(reportData [64]byte) ([]uint8, error)
	// Product returns AMD SEV-related CPU information of the calling CPU.
	//
	// Deprecated: Use abi.ExtraPlatformInfoGUID in the raw quote certificate table.
	Product() *pb.SevProduct
}

// UseDefaultSevGuest returns true iff -sev_guest_device_path=default.
func UseDefaultSevGuest() bool {
	return *sevGuestPath == "default"
}

func message(d Device, command uintptr, req *labi.SnpUserGuestRequest) error {
	result, err := d.Ioctl(command, req)
	if err != nil {
		// The ioctl could have failed with a firmware error that
		// indicates a problem certificate length. We need to
		// communicate that specifically.
		if req.FwErr != 0 {
			return &abi.SevFirmwareErr{Status: abi.SevFirmwareStatus(req.FwErr)}
		}
		return err
	}
	if result != uintptr(labi.EsOk) {
		return &labi.SevEsErr{Result: labi.EsResult(result)}
	}
	return nil
}

// GetRawReportAtVmpl requests for an attestation report at the given VMPL that incorporates the
// given user data.
//
// Deprecated: Use LeveledQuoteProvider.
func GetRawReportAtVmpl(d Device, reportData [64]byte, vmpl int) ([]byte, error) {
	var snpReportRsp labi.SnpReportRespABI
	userGuestReq := labi.SnpUserGuestRequest{
		ReqData: &labi.SnpReportReqABI{
			ReportData: reportData,
			Vmpl:       uint32(vmpl),
		},
		RespData: &snpReportRsp,
	}
	if err := message(d, labi.IocSnpGetReport, &userGuestReq); err != nil {
		return nil, err
	}
	return snpReportRsp.Data[:abi.ReportSize], nil
}

// GetRawReport requests for an attestation report at VMPL0 that incorporates the given user data.
//
// Deprecated: Use QuoteProvider.
func GetRawReport(d Device, reportData [64]byte) ([]byte, error) {
	return GetRawReportAtVmpl(d, reportData, 0)
}

// GetReportAtVmpl gets an attestation report at the given VMPL into its protobuf representation.
//
// Deprecated: Use GetQuoteProtoAtLevel.
func GetReportAtVmpl(d Device, reportData [64]byte, vmpl int) (*pb.Report, error) {
	data, err := GetRawReportAtVmpl(d, reportData, vmpl)
	if err != nil {
		return nil, err
	}
	return abi.ReportToProto(data)
}

// GetReport gets an attestation report at VMPL0 into its protobuf representation.
//
// Deprecated: Use GetQuoteProto.
func GetReport(d Device, reportData [64]byte) (*pb.Report, error) {
	return GetReportAtVmpl(d, reportData, 0)
}

// getExtendedReportIn issues a GetExtendedReport command to the sev-guest driver with reportData
// input and certs as a destination for certificate data. If certs is empty, this function returns
// the expected size of certs as its second result value. If certs is non-empty, this function
// returns the signed attestation report containing reportData and the certificate chain for the
// report's endorsement key.
func getExtendedReportIn(d Device, reportData [64]byte, vmpl int, certs []byte) ([]byte, uint32, error) {
	var snpReportRsp labi.SnpReportRespABI
	snpExtReportReq := labi.SnpExtendedReportReq{
		Data: labi.SnpReportReqABI{
			ReportData: reportData,
			Vmpl:       uint32(vmpl),
		},
		Certs:       certs,
		CertsLength: uint32(len(certs)),
	}
	userGuestReq := labi.SnpUserGuestRequest{
		ReqData:  &snpExtReportReq,
		RespData: &snpReportRsp,
	}
	// Query the length required for certs.
	if err := message(d, labi.IocSnpGetExtendedReport, &userGuestReq); err != nil {
		var fwErr *abi.SevFirmwareErr
		if errors.As(err, &fwErr) && fwErr.Status == abi.GuestRequestInvalidLength {
			return nil, snpExtReportReq.CertsLength, nil
		}
		return nil, 0, err
	}
	return snpReportRsp.Data[:abi.ReportSize], snpExtReportReq.CertsLength, nil
}

// queryCertificateLength requests the required memory size in bytes to represent all certificates
// returned by an extended guest request.
func queryCertificateLength(d Device, vmpl int) (uint32, error) {
	_, length, err := getExtendedReportIn(d, [64]byte{}, vmpl, []byte{})
	if err != nil {
		return 0, err
	}
	return length, nil
}

// GetRawExtendedReportAtVmpl requests for an attestation report that incorporates the given user
// data at the given VMPL, and additional key certificate information.
//
// Deprecated: Use LeveledQuoteProvider.
func GetRawExtendedReportAtVmpl(d Device, reportData [64]byte, vmpl int) ([]byte, []byte, error) {
	length, err := queryCertificateLength(d, vmpl)
	if err != nil {
		return nil, nil, fmt.Errorf("error querying certificate length: %v", err)
	}
	certs := make([]byte, length)
	report, _, err := getExtendedReportIn(d, reportData, vmpl, certs)
	if err != nil {
		return nil, nil, err
	}
	return report, certs, nil
}

// GetRawExtendedReport requests for an attestation report that incorporates the given user data,
// and additional key certificate information.
//
// Deprecated: Use QuoteProvider.
func GetRawExtendedReport(d Device, reportData [64]byte) ([]byte, []byte, error) {
	return GetRawExtendedReportAtVmpl(d, reportData, 0)
}

// GetQuoteProto uses the given QuoteProvider to return the
// protobuf representation of an attestation report with cached
// certificate chain.
func GetQuoteProto(qp QuoteProvider, reportData [64]byte) (*pb.Attestation, error) {
	reportcerts, err := qp.GetRawQuote(reportData)
	if err != nil {
		return nil, err
	}
	attestation, err := abi.ReportCertsToProto(reportcerts)
	if err != nil {
		return nil, err
	}
	// TODO(Issue#109): Remove when Product is removed.
	attestation.Product = qp.Product()
	return attestation, nil
}

// GetQuoteProtoAtLevel uses the given LeveledQuoteProvider to return the
// protobuf representation of an attestation report at a given VMPL with cached
// certificate chain.
func GetQuoteProtoAtLevel(qp LeveledQuoteProvider, reportData [64]byte, vmpl uint) (*pb.Attestation, error) {
	reportcerts, err := qp.GetRawQuoteAtLevel(reportData, vmpl)
	if err != nil {
		return nil, err
	}
	attestation, err := abi.ReportCertsToProto(reportcerts)
	if err != nil {
		return nil, err
	}
	attestation.Product = qp.Product()
	return attestation, nil
}

// GetExtendedReportAtVmpl gets an extended attestation report at the given VMPL into a structured type.
//
// Deprecated: Use GetQuoteProtoAtLevel
func GetExtendedReportAtVmpl(d Device, reportData [64]byte, vmpl int) (*pb.Attestation, error) {
	reportBytes, certBytes, err := GetRawExtendedReportAtVmpl(d, reportData, vmpl)
	if err != nil {
		return nil, err
	}

	report, err := abi.ReportToProto(reportBytes)
	if err != nil {
		return nil, err
	}

	certs := new(abi.CertTable)
	if err := certs.Unmarshal(certBytes); err != nil {
		return nil, err
	}
	return &pb.Attestation{
		Report:           report,
		CertificateChain: certs.Proto(),
		Product:          d.Product(),
	}, nil
}

// GetExtendedReport gets an extended attestation report at VMPL0 into a structured type.
//
// Deprecated: Use GetQuoteProto.
func GetExtendedReport(d Device, reportData [64]byte) (*pb.Attestation, error) {
	return GetExtendedReportAtVmpl(d, reportData, 0)
}

// GuestFieldSelect represents which guest-provided information will be mixed into a derived key.
type GuestFieldSelect struct {
	TCBVersion  bool
	GuestSVN    bool
	Measurement bool
	FamilyID    bool
	ImageID     bool
	GuestPolicy bool
}

// SnpDerivedKeyReq represents a request to the SEV guest device to derive a key from specified
// information.
type SnpDerivedKeyReq struct {
	// UseVCEK determines if the derived key will be based on VCEK or VMRK. This is opposite from the
	// ABI's ROOT_KEY_SELECT to avoid accidentally making an unsafe choice in a multitenant
	// environment.
	UseVCEK          bool
	GuestFieldSelect GuestFieldSelect
	// Vmpl to mix into the key. Must be greater than or equal to current Vmpl.
	Vmpl uint32
	// GuestSVN to mix into the key. Must be less than or equal to GuestSVN at launch.
	GuestSVN uint32
	// TCBVersion to mix into the key. Must be less than or equal to the CommittedTcb.
	TCBVersion uint64
}

// ABI returns the SNP ABI-specified uint64 bitmask of guest field selection.
func (g GuestFieldSelect) ABI() uint64 {
	var value uint64
	if g.TCBVersion {
		value |= uint64(1 << 5)
	}
	if g.GuestSVN {
		value |= uint64(1 << 4)
	}
	if g.Measurement {
		value |= uint64(1 << 3)
	}
	if g.FamilyID {
		value |= uint64(1 << 2)
	}
	if g.ImageID {
		value |= uint64(1 << 1)
	}
	if g.GuestPolicy {
		value |= uint64(1 << 0)
	}
	return value
}

// GetDerivedKeyAcknowledgingItsLimitations returns 32 bytes of key material that the AMD security
// processor derives from the given parameters. Security limitations of this command are described
// more in the project README.
func GetDerivedKeyAcknowledgingItsLimitations(d Device, request *SnpDerivedKeyReq) (*labi.SnpDerivedKeyRespABI, error) {
	response := &labi.SnpDerivedKeyRespABI{}
	rootKeySelect := uint32(1)
	if request.UseVCEK {
		rootKeySelect = 0
	}
	guestRequest := &labi.SnpUserGuestRequest{
		ReqData: &labi.SnpDerivedKeyReqABI{
			RootKeySelect:    rootKeySelect,
			GuestFieldSelect: request.GuestFieldSelect.ABI(),
			Vmpl:             request.Vmpl,
			GuestSVN:         request.GuestSVN,
			TCBVersion:       request.TCBVersion,
		},
		RespData: response,
	}
	if err := message(d, labi.IocSnpGetDerivedKey, guestRequest); err != nil {
		return nil, fmt.Errorf("error getting derived key: %v", err)
	}
	return response, nil
}