COCOS-439 - TDX Attestation support (#459)
CI / ci (push) Has been cancelled

* inital tdx attestation support

* fix fetching and verification errors

* fix tests

* fix lint problems

* fix reading attestation policy

* add tdx package

* remove Makefile configuration for staticly built binaries
This commit is contained in:
Danko Miladinovic
2025-07-01 00:44:29 +02:00
committed by GitHub
parent 79c66a89c3
commit 9c8ddfd2b1
26 changed files with 1658 additions and 954 deletions
+124 -25
View File
@@ -9,6 +9,7 @@ import (
"crypto/sha256"
"crypto/sha512"
"encoding/hex"
"encoding/json"
"fmt"
"io"
"os"
@@ -16,6 +17,7 @@ import (
"github.com/absmach/magistrala/pkg/errors"
"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/proto/check"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-tpm-tools/client"
"github.com/google/go-tpm-tools/proto/attest"
@@ -26,11 +28,15 @@ import (
"github.com/ultravioletrs/cocos/pkg/attestation"
"github.com/ultravioletrs/cocos/pkg/attestation/quoteprovider"
"golang.org/x/crypto/sha3"
"google.golang.org/protobuf/encoding/protojson"
"google.golang.org/protobuf/encoding/prototext"
"google.golang.org/protobuf/proto"
)
var _ attestation.Provider = (*provider)(nil)
var (
_ attestation.Provider = (*provider)(nil)
_ attestation.Verifier = (*verifier)(nil)
)
const (
eventLog = "/sys/kernel/security/tpm0/binary_bios_measurements"
@@ -43,10 +49,15 @@ const (
)
var (
ExternalTPM io.ReadWriteCloser
ErrNoHashAlgo = errors.New("hash algo is not supported")
ErrFetchQuote = errors.New("failed to fetch vTPM quote")
ErrFetchAzureToken = errors.New("failed to fetch Azure token")
ExternalTPM io.ReadWriteCloser
ErrNoHashAlgo = errors.New("hash algo is not supported")
ErrFetchQuote = errors.New("failed to fetch vTPM quote")
ErrAttestationPolicyOpen = errors.New("failed to open Attestation Policy file")
ErrAttestationPolicyDecode = errors.New("failed to decode Attestation Policy file")
ErrAttestationPolicyMissing = errors.New("failed due to missing Attestation Policy file")
ErrProtoMarshalFailed = errors.New("failed to marshal protojson")
ErrJsonMarshalFailed = errors.New("failed to marshal json")
ErrJsonUnarshalFailed = errors.New("failed to unmarshal json")
)
type tpm struct {
@@ -97,15 +108,13 @@ type provider struct {
pubKey []byte
teeAttestaion bool
vmpl uint
writer io.Writer
}
func New(pubKey []byte, teeAttestation bool, vmpl uint, writer io.Writer) attestation.Provider {
func NewProvider(pubKey []byte, teeAttestation bool, vmpl uint) attestation.Provider {
return &provider{
pubKey: pubKey,
teeAttestaion: teeAttestation,
vmpl: vmpl,
writer: writer,
}
}
@@ -126,26 +135,61 @@ func (v provider) VTpmAttestation(vTpmNonce []byte) ([]byte, error) {
return proto.Marshal(quote)
}
func (v provider) VerifTeeAttestation(report []byte, teeNonce []byte) error {
func (v provider) AzureAttestationToken(tokenNonce []byte) ([]byte, error) {
return nil, errors.New("Azure attestation token is not supported")
}
type verifier struct {
pubKey []byte
writer io.Writer
Policy *attestation.Config
}
func NewVerifier(pubKey []byte, writer io.Writer) attestation.Verifier {
policy := &attestation.Config{
Config: &check.Config{Policy: &check.Policy{}, RootOfTrust: &check.RootOfTrust{}},
PcrConfig: &attestation.PcrConfig{},
}
return &verifier{
pubKey: pubKey,
writer: writer,
Policy: policy,
}
}
func NewVerifierWithPolicy(pubKey []byte, writer io.Writer, policy *attestation.Config) attestation.Verifier {
if policy == nil {
return NewVerifier(pubKey, writer)
}
return &verifier{
pubKey: pubKey,
writer: writer,
Policy: policy,
}
}
func (v verifier) VerifTeeAttestation(report []byte, teeNonce []byte) error {
attestReport, err := abi.ReportToProto(report)
if err != nil {
return errors.Wrap(fmt.Errorf("failed to convert TEE report to proto"), err)
}
attestationReport := sevsnp.Attestation{Report: attestReport, CertificateChain: nil}
return quoteprovider.VerifyAttestationReportTLS(&attestationReport, teeNonce)
return quoteprovider.VerifyAttestationReportTLS(&attestationReport, teeNonce, v.Policy)
}
func (v provider) VerifVTpmAttestation(report []byte, vTpmNonce []byte) error {
return VerifyQuote(report, v.pubKey, vTpmNonce, v.writer)
func (v verifier) VerifVTpmAttestation(report []byte, vTpmNonce []byte) error {
return VerifyQuote(report, v.pubKey, vTpmNonce, v.writer, v.Policy)
}
func (v provider) VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error {
return VTPMVerify(report, v.pubKey, teeNonce, vTpmNonce, v.writer)
func (v verifier) VerifyAttestation(report []byte, teeNonce []byte, vTpmNonce []byte) error {
return VTPMVerify(report, v.pubKey, teeNonce, vTpmNonce, v.writer, v.Policy)
}
func (v provider) AzureAttestationToken(tokenNonce []byte) ([]byte, error) {
return nil, errors.New("Azure attestation token is not supported")
func (v verifier) JSONToPolicy(path string) error {
return ReadPolicy(path, v.Policy)
}
func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool, vmpl uint) ([]byte, error) {
@@ -164,8 +208,8 @@ func Attest(teeNonce []byte, vTPMNonce []byte, teeAttestaion bool, vmpl uint) ([
return marshalQuote(attestation)
}
func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byte, writer io.Writer) error {
if err := VerifyQuote(quote, pubKeyTLS, vtpmNonce, writer); err != nil {
func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byte, writer io.Writer, policy *attestation.Config) error {
if err := VerifyQuote(quote, pubKeyTLS, vtpmNonce, writer, policy); err != nil {
return fmt.Errorf("failed to verify vTPM quote: %v", err)
}
@@ -176,14 +220,14 @@ func VTPMVerify(quote []byte, pubKeyTLS []byte, teeNonce []byte, vtpmNonce []byt
return errors.Wrap(fmt.Errorf("failed to unmarshal quote"), err)
}
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), teeNonce); err != nil {
if err := quoteprovider.VerifyAttestationReportTLS(attestation.GetSevSnpAttestation(), teeNonce, policy); err != nil {
return fmt.Errorf("failed to verify TEE attestation report: %v", err)
}
return nil
}
func VerifyQuote(quote []byte, pubKeyTLS []byte, vtpmNonce []byte, writer io.Writer) error {
func VerifyQuote(quote []byte, pubKeyTLS []byte, vtpmNonce []byte, writer io.Writer, policy *attestation.Config) error {
attestation := &attest.Attestation{}
err := proto.Unmarshal(quote, attestation)
@@ -209,7 +253,7 @@ func VerifyQuote(quote []byte, pubKeyTLS []byte, vtpmNonce []byte, writer io.Wri
s256, s384 := calculatePCRTLSKey(pubKeyTLS)
if err := checkExpectedPCRValues(attestation, s256, s384); err != nil {
if err := checkExpectedPCRValues(attestation, s256, s384, policy); err != nil {
return fmt.Errorf("PCR values do not match expected PCR values: %w", err)
}
@@ -286,7 +330,7 @@ func addTEEAttestation(attestation *attest.Attestation, nonce []byte, vmpl uint)
return nil
}
func checkExpectedPCRValues(attQuote *attest.Attestation, ePcr256, ePcr384 []byte) error {
func checkExpectedPCRValues(attQuote *attest.Attestation, ePcr256, ePcr384 []byte, policy *attestation.Config) error {
quotes := attQuote.GetQuotes()
for i := range quotes {
quote := quotes[i]
@@ -294,21 +338,21 @@ func checkExpectedPCRValues(attQuote *attest.Attestation, ePcr256, ePcr384 []byt
var pcr15 []byte
switch quote.Pcrs.Hash {
case ptpm.HashAlgo_SHA256:
pcrMap = attestation.AttestationPolicy.PcrConfig.PCRValues.Sha256
pcrMap = policy.PcrConfig.PCRValues.Sha256
if ePcr256 == nil {
pcr15 = make([]byte, 32)
} else {
pcr15 = ePcr256
}
case ptpm.HashAlgo_SHA384:
pcrMap = attestation.AttestationPolicy.PcrConfig.PCRValues.Sha384
pcrMap = policy.PcrConfig.PCRValues.Sha384
if ePcr384 == nil {
pcr15 = make([]byte, 48)
} else {
pcr15 = ePcr384
}
case ptpm.HashAlgo_SHA1:
pcrMap = attestation.AttestationPolicy.PcrConfig.PCRValues.Sha1
pcrMap = policy.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", ptpm.HashAlgo_name[int32(quote.Pcrs.Hash)]))
@@ -383,3 +427,58 @@ func GetPCRSHA256Value(index int) ([]byte, error) {
func GetPCRSHA384Value(index int) ([]byte, error) {
return getPCRValue(index, tpm2.AlgSHA384)
}
func ReadPolicy(policyPath string, attestationConfiguration *attestation.Config) error {
if policyPath != "" {
policyData, err := os.ReadFile(policyPath)
if err != nil {
return errors.Wrap(ErrAttestationPolicyOpen, err)
}
return ReadPolicyFromByte(policyData, attestationConfiguration)
}
return ErrAttestationPolicyMissing
}
func ReadPolicyFromByte(policyData []byte, attestationConfiguration *attestation.Config) error {
unmarshalOptions := protojson.UnmarshalOptions{AllowPartial: true, DiscardUnknown: true}
if err := unmarshalOptions.Unmarshal(policyData, attestationConfiguration.Config); err != nil {
return errors.Wrap(ErrAttestationPolicyDecode, err)
}
if err := json.Unmarshal(policyData, attestationConfiguration.PcrConfig); err != nil {
return errors.Wrap(ErrAttestationPolicyDecode, err)
}
return nil
}
func ConvertPolicyToJSON(attestationConfiguration *attestation.Config) ([]byte, error) {
pbJson, err := protojson.Marshal(attestationConfiguration.Config)
if err != nil {
return nil, errors.Wrap(ErrProtoMarshalFailed, err)
}
var pbMap map[string]interface{}
if err := json.Unmarshal(pbJson, &pbMap); err != nil {
return nil, errors.Wrap(ErrJsonUnarshalFailed, err)
}
pcrJson, err := json.Marshal(attestationConfiguration.PcrConfig)
if err != nil {
return nil, errors.Wrap(ErrJsonMarshalFailed, err)
}
var pcrMap map[string]interface{}
if err := json.Unmarshal(pcrJson, &pcrMap); err != nil {
return nil, errors.Wrap(ErrJsonUnarshalFailed, err)
}
for k, v := range pcrMap {
pbMap[k] = v
}
return json.MarshalIndent(pbMap, "", " ")
}