mirror of
https://github.com/ultravioletrs/cocos.git
synced 2026-06-23 04:10:25 +00:00
COCOS-391- GCP Attestation policy (#405)
* Add AgentGrpcHost configuration to agent server Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Add SHA1 support to PcrValues and implement GCP attestation functions Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Add GCP attestation policy and OVMF download commands Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Add vTPM attestation support and update protobuf versions Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Remove Host field from AgentConfig and update related references Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Update GCP attestation policy to accept vCPU count as an argument Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Add SHA512 digest verification for OVMF file in GCP download command Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Update OVMF object name format in GCP attestation package Signed-off-by: Sammy Oina <sammyoina@gmail.com> * Refactor attestation policy structure to use nested Config field Signed-off-by: Sammy Oina <sammyoina@gmail.com> --------- Signed-off-by: Sammy Oina <sammyoina@gmail.com>
This commit is contained in:
committed by
GitHub
parent
ebc8f1bba4
commit
c14f1d7b6c
@@ -31,6 +31,7 @@ var (
|
||||
type PcrValues struct {
|
||||
Sha256 map[string]string `json:"sha256"`
|
||||
Sha384 map[string]string `json:"sha384"`
|
||||
Sha1 map[string]string `json:"sha1"`
|
||||
}
|
||||
|
||||
type PcrConfig struct {
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
// Copyright (c) Ultraviolet
|
||||
// SPDX-License-Identifier: Apache-2.0
|
||||
|
||||
package gcp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"cloud.google.com/go/storage"
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/google/gce-tcb-verifier/proto/endorsement"
|
||||
"github.com/google/go-sev-guest/proto/check"
|
||||
"github.com/google/go-sev-guest/proto/sevsnp"
|
||||
"github.com/google/go-sev-guest/tools/lib/report"
|
||||
config "github.com/ultravioletrs/cocos/pkg/attestation"
|
||||
)
|
||||
|
||||
const (
|
||||
// Offset of the 384-bit measurement in the report.
|
||||
// The measurement is 48 bytes long and starts at offset 0x90.
|
||||
// https://www.amd.com/content/dam/amd/en/documents/epyc-technical-docs/specifications/56860.pdf page 56
|
||||
measurementOffset = 0x90
|
||||
measurementSize = 48
|
||||
bucketName = "gce_tcb_integrity"
|
||||
objectName = "ovmf_x64_csm/sevsnp/%s.binarypb"
|
||||
ovmfObjectName = "ovmf_x64_csm/%s.fd"
|
||||
)
|
||||
|
||||
func Extract384BitMeasurement(attestation *sevsnp.Attestation) (string, error) {
|
||||
if attestation == nil {
|
||||
return "", fmt.Errorf("report is nil")
|
||||
}
|
||||
|
||||
reportBin, err := report.Transform(attestation, "bin")
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to transform report to binary: %v", err)
|
||||
}
|
||||
|
||||
if len(reportBin) < measurementOffset+measurementSize {
|
||||
return "", fmt.Errorf("report is too short to contain the 384-bit measurement")
|
||||
}
|
||||
|
||||
measurement := reportBin[measurementOffset : measurementOffset+measurementSize]
|
||||
return fmt.Sprintf("%x", measurement), nil
|
||||
}
|
||||
|
||||
func GetLaunchEndorsement(ctx context.Context, measurement384 string) (*endorsement.VMGoldenMeasurement, error) {
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to create storage client: %v", err)
|
||||
}
|
||||
|
||||
reader, err := client.Bucket(bucketName).Object(fmt.Sprintf(objectName, measurement384)).NewReader(ctx)
|
||||
if err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to create reader: %v", err)
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
launchEndorsements, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to read object: %v", err)
|
||||
}
|
||||
|
||||
var endorsementPB endorsement.VMLaunchEndorsement
|
||||
if err := proto.Unmarshal(launchEndorsements, &endorsementPB); err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to unmarshal launch endorsement: %v", err)
|
||||
}
|
||||
|
||||
var goldenUEFI endorsement.VMGoldenMeasurement
|
||||
if err := proto.Unmarshal(endorsementPB.SerializedUefiGolden, &goldenUEFI); err != nil {
|
||||
return &endorsement.VMGoldenMeasurement{}, fmt.Errorf("failed to unmarshal golden UEFI: %v", err)
|
||||
}
|
||||
|
||||
return &goldenUEFI, nil
|
||||
}
|
||||
|
||||
func GenerateAttestationPolicy(endorsement *endorsement.VMGoldenMeasurement, vcpuNum uint32) (*config.Config, error) {
|
||||
attestationPolicy := config.Config{PcrConfig: &config.PcrConfig{}, Config: &check.Config{RootOfTrust: &check.RootOfTrust{}, Policy: &check.Policy{}}}
|
||||
attestationPolicy.Config.Policy.Policy = endorsement.SevSnp.Policy
|
||||
attestationPolicy.Config.Policy.Measurement = endorsement.SevSnp.Measurements[vcpuNum]
|
||||
attestationPolicy.Config.RootOfTrust.DisallowNetwork = false
|
||||
attestationPolicy.Config.RootOfTrust.CheckCrl = true
|
||||
attestationPolicy.Config.RootOfTrust.Product = "Milan"
|
||||
attestationPolicy.Config.RootOfTrust.ProductLine = "Milan"
|
||||
|
||||
return &attestationPolicy, nil
|
||||
}
|
||||
|
||||
func DownloadOvmfFile(ctx context.Context, digest string) ([]byte, error) {
|
||||
client, err := storage.NewClient(ctx)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to create storage client: %v", err)
|
||||
}
|
||||
|
||||
reader, err := client.Bucket(bucketName).Object(fmt.Sprintf(ovmfObjectName, digest)).NewReader(ctx)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to create reader: %v", err)
|
||||
}
|
||||
|
||||
defer reader.Close()
|
||||
|
||||
ovmf, err := io.ReadAll(reader)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to read object: %v", err)
|
||||
}
|
||||
|
||||
return ovmf, nil
|
||||
}
|
||||
@@ -126,7 +126,7 @@ func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byt
|
||||
|
||||
err := proto.Unmarshal(quote, attestation)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to unmarshal quote: %v", err)
|
||||
return errors.Wrap(fmt.Errorf("failed to unmarshal quote"), err)
|
||||
}
|
||||
|
||||
ak := attestation.GetAkPub()
|
||||
@@ -142,7 +142,7 @@ func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byt
|
||||
|
||||
reportData, err := createTEEAttestationReportNonce(pubKeyTLS, ak, teeNonce)
|
||||
if err != nil {
|
||||
return fmt.Errorf("fail to calculate report data: %v", err)
|
||||
return errors.Wrap(fmt.Errorf("failed to create TEE attestation report nonce"), err)
|
||||
}
|
||||
|
||||
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), reportData); err != nil {
|
||||
@@ -151,7 +151,7 @@ func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byt
|
||||
|
||||
_, err = server.VerifyAttestation(attestation, server.VerifyOpts{Nonce: vtpmNonce, TrustedAKs: []crypto.PublicKey{cryptoPub}})
|
||||
if err != nil {
|
||||
return fmt.Errorf("verifying attestation: %w", err)
|
||||
return errors.Wrap(fmt.Errorf("failed to verify attestation"), err)
|
||||
}
|
||||
|
||||
s256, s384 := calculatePCRTLSKey(pubKeyTLS)
|
||||
@@ -196,7 +196,7 @@ func createTEEAttestationReportNonce(pubKeyTLS []byte, ak []byte, nonce []byte)
|
||||
func marshalQuote(attestation *attest.Attestation) ([]byte, error) {
|
||||
out, err := proto.Marshal(attestation)
|
||||
if err != nil {
|
||||
return []byte{}, fmt.Errorf("failed to marshal vTPM attestation report: %v", err)
|
||||
return []byte{}, errors.Wrap(fmt.Errorf("failed to marshal vTPM attestation report"), err)
|
||||
}
|
||||
|
||||
return out, nil
|
||||
@@ -211,7 +211,7 @@ func fetchVTPMQuote(nonce []byte) (*attest.Attestation, error) {
|
||||
|
||||
attestationKey, err := client.AttestationKeyRSA(rwc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create attestation key: %v", err)
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to create attestation key: %v", err), err)
|
||||
}
|
||||
defer attestationKey.Close()
|
||||
|
||||
@@ -222,12 +222,12 @@ func fetchVTPMQuote(nonce []byte) (*attest.Attestation, error) {
|
||||
|
||||
attestOpts.TCGEventLog, err = client.GetEventLog(rwc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to retrieve TCG Event Log: %w", err)
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to retrieve TCG Event Log: %v", err), err)
|
||||
}
|
||||
|
||||
attestation, err := attestationKey.Attest(attestOpts)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to collect attestation report: %v", err)
|
||||
return nil, errors.Wrap(fmt.Errorf("failed to attest: %v", err), err)
|
||||
}
|
||||
|
||||
return attestation, nil
|
||||
@@ -241,7 +241,7 @@ func addTEEAttestation(attestation *attest.Attestation, nonce []byte) (*attest.A
|
||||
|
||||
extReport, err := abi.ReportCertsToProto(rawTeeAttestation)
|
||||
if err != nil {
|
||||
return attestation, fmt.Errorf("failed to export the TEE report: %v", err)
|
||||
return attestation, errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
|
||||
}
|
||||
attestation.TeeAttestation = &attest.Attestation_SevSnpAttestation{
|
||||
SevSnpAttestation: extReport,
|
||||
@@ -250,7 +250,7 @@ func addTEEAttestation(attestation *attest.Attestation, nonce []byte) (*attest.A
|
||||
return attestation, nil
|
||||
}
|
||||
|
||||
func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256 []byte, ePcr384 []byte) error {
|
||||
func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256, ePcr384 []byte) error {
|
||||
quotes := attestation.GetQuotes()
|
||||
for i := range quotes {
|
||||
quote := quotes[i]
|
||||
@@ -263,6 +263,9 @@ func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256 []byte, ePc
|
||||
case tpm.HashAlgo_SHA384:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha384
|
||||
pcr15 = ePcr384
|
||||
case tpm.HashAlgo_SHA1:
|
||||
pcrMap = config.AttestationPolicy.PcrConfig.PCRValues.Sha1
|
||||
pcr15 = []byte{0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
|
||||
default:
|
||||
return errors.Wrap(ErrNoHashAlgo, fmt.Errorf("algo: %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)]))
|
||||
}
|
||||
@@ -275,11 +278,11 @@ func checkExpectedPCRValues(attestation *attest.Attestation, ePcr256 []byte, ePc
|
||||
for i, v := range pcrMap {
|
||||
index, err := strconv.ParseInt(i, 10, 32)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting PCR index to int32: %v\n", err)
|
||||
return errors.Wrap(fmt.Errorf("error converting PCR index to int32"), err)
|
||||
}
|
||||
value, err := hex.DecodeString(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("error converting PCR value to byte: %v\n", err)
|
||||
return errors.Wrap(fmt.Errorf("error converting PCR value to byte"), err)
|
||||
}
|
||||
if !bytes.Equal(quote.Pcrs.Pcrs[uint32(index)], value) {
|
||||
return fmt.Errorf("for algo %s PCR[%d] expected %s but found %s", tpm.HashAlgo_name[int32(quote.Pcrs.Hash)], index, hex.EncodeToString(value), hex.EncodeToString(quote.Pcrs.Pcrs[uint32(index)]))
|
||||
|
||||
Reference in New Issue
Block a user