kops/vendor/github.com/google/go-sev-guest/client/client.go

263 lines
8.8 KiB
Go

// 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 (
"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"
"github.com/pkg/errors"
)
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
}
// 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.
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.
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.
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.
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.
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.
func GetRawExtendedReport(d Device, reportData [64]byte) ([]byte, []byte, error) {
return GetRawExtendedReportAtVmpl(d, reportData, 0)
}
// GetExtendedReportAtVmpl gets an extended attestation report at the given VMPL into a structured type.
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.
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
}