mirror of https://github.com/kubernetes/kops.git
263 lines
8.8 KiB
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
|
|
}
|