Skip to content

Commit

Permalink
attestation: add TDX validator
Browse files Browse the repository at this point in the history
This adds a basic implementation of a TDX-capable validator. It's not yet used throughout the code, and it doesn't implement certificate extensions yet.
  • Loading branch information
msanft authored and Freax13 committed Aug 20, 2024
1 parent d0de4f8 commit 0e5edc1
Show file tree
Hide file tree
Showing 3 changed files with 187 additions and 0 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
-----BEGIN CERTIFICATE-----
MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw
aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv
cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ
BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG
A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0
aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT
AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7
1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB
uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ
MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50
ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV
Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI
KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg
AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=
-----END CERTIFICATE-----
170 changes: 170 additions & 0 deletions internal/attestation/tdx/validator.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// Copyright 2024 Edgeless Systems GmbH
// SPDX-License-Identifier: AGPL-3.0-only

package tdx

import (
"context"
"crypto/x509"
_ "embed"
"encoding/asn1"
"encoding/hex"
"fmt"
"log/slog"

"github.com/edgelesssys/contrast/internal/attestation/reportdata"
"github.com/edgelesssys/contrast/internal/oid"
"github.com/google/go-tdx-guest/abi"
"github.com/google/go-tdx-guest/proto/tdx"
"github.com/google/go-tdx-guest/validate"
"github.com/google/go-tdx-guest/verify"
"github.com/google/go-tdx-guest/verify/trust"
"github.com/prometheus/client_golang/prometheus"
"google.golang.org/protobuf/proto"
)

// Even though the vendored file has "SGX" in its name, it is the general "Provisioning Certificate for ECDSA Attestation"
// from Intel and used for both SGX *and* TDX.
//
// See https://api.portal.trustedservices.intel.com/content/documentation.html#pcs for more information.
//
// File Source: https://certificates.trustedservices.intel.com/Intel_SGX_Provisioning_Certification_RootCA.pem
//
//go:embed Intel_SGX_Provisioning_Certification_RootCA.pem
var tdxRootCert []byte

// Validator validates attestation statements.
type Validator struct {
validateOptsGen validateOptsGenerator
callbackers []validateCallbacker
certGetter trust.HTTPSGetter
logger *slog.Logger
metrics metrics
}

type metrics struct {
attestationFailures prometheus.Counter
}

type validateCallbacker interface {
ValidateCallback(ctx context.Context, quote *tdx.QuoteV4, validatorOID asn1.ObjectIdentifier,
reportRaw, nonce, peerPublicKey []byte) error
}

type validateOptsGenerator interface {
TDXValidateOpts(report *tdx.QuoteV4) (*validate.Options, error)
}

// StaticValidateOptsGenerator returns validate.Options generator that returns
// static validation options.
type StaticValidateOptsGenerator struct {
Opts *validate.Options
}

// TDXValidateOpts return the TDX validation options.
func (v *StaticValidateOptsGenerator) TDXValidateOpts(_ *tdx.QuoteV4) (*validate.Options, error) {
return v.Opts, nil
}

// NewValidator returns a new Validator.
func NewValidator(optsGen validateOptsGenerator, certGetter trust.HTTPSGetter, log *slog.Logger) *Validator {
return &Validator{
validateOptsGen: optsGen,
certGetter: certGetter,
logger: log,
}
}

// NewValidatorWithCallbacks returns a new Validator with callbacks.
func NewValidatorWithCallbacks(optsGen validateOptsGenerator, certGetter trust.HTTPSGetter, log *slog.Logger, attestationFailures prometheus.Counter, callbacks ...validateCallbacker) *Validator {
v := NewValidator(optsGen, certGetter, log)
v.callbackers = callbacks
v.metrics = metrics{attestationFailures: attestationFailures}
return v
}

// OID returns the OID of the validator.
func (v *Validator) OID() asn1.ObjectIdentifier {
return oid.RawTDXReport
}

// Validate a TDX attestation.
func (v *Validator) Validate(ctx context.Context, attDocRaw []byte, nonce []byte, peerPublicKey []byte) (err error) {
v.logger.Info("Validate called", "nonce", hex.EncodeToString(nonce))
defer func() {
if err != nil {
v.logger.Error("Failed to validate attestation document", "err", err)
if v.metrics.attestationFailures != nil {
v.metrics.attestationFailures.Inc()
}
}
}()

// Parse the attestation document.

quote := &tdx.QuoteV4{}
if err := proto.Unmarshal(attDocRaw, quote); err != nil {
return fmt.Errorf("unmarshaling attestation: %w", err)
}

quoteRaw, err := abi.QuoteToAbiBytes(quote)
if err != nil {
return fmt.Errorf("converting quote to abi format: %w", err)
}
v.logger.Info("Quote decoded", "quoteRaw", hex.EncodeToString(quoteRaw))

// Build the verification options.

verifyOpts := verify.DefaultOptions()
rootCerts, err := trustedRoots()
if err != nil {
return fmt.Errorf("getting trusted roots: %w", err)
}
verifyOpts.TrustedRoots = rootCerts
verifyOpts.CheckRevocations = true
verifyOpts.Getter = v.certGetter

// Verify the report signature.

if err := verify.TdxQuote(quote, verifyOpts); err != nil {
return fmt.Errorf("verifying report signature: %w", err)
}
v.logger.Info("Successfully verified report signature")

// Build the validation options.

reportDataExpected := reportdata.Construct(peerPublicKey, nonce)
validateOpts, err := v.validateOptsGen.TDXValidateOpts(quote)
if err != nil {
return fmt.Errorf("generating validation options: %w", err)
}
validateOpts.TdQuoteBodyOptions.ReportData = reportDataExpected[:]

// Validate the report data.

if err := validate.TdxQuote(quote, validateOpts); err != nil {
return fmt.Errorf("validating report data: %w", err)
}
v.logger.Info("Successfully validated report data")

// Run callbacks.

for _, callbacker := range v.callbackers {
if err := callbacker.ValidateCallback(
ctx, quote, v.OID(), quoteRaw, nonce, peerPublicKey,
); err != nil {
return fmt.Errorf("callback failed: %w", err)
}
}

v.logger.Info("Validate finished successfully")
return nil
}

func trustedRoots() (*x509.CertPool, error) {
rootCerts := x509.NewCertPool()
if ok := rootCerts.AppendCertsFromPEM(tdxRootCert); !ok {
return nil, fmt.Errorf("failed to append root certificate")
}
return rootCerts, nil
}
1 change: 1 addition & 0 deletions packages/by-name/contrast/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ buildGoModule rec {
(path.append root "internal/manifest/Milan.pem")
(path.append root "internal/manifest/Genoa.pem")
(path.append root "nodeinstaller")
(path.append root "internal/attestation/tdx/Intel_SGX_Provisioning_Certification_RootCA.pem")
(fileset.difference (fileset.fileFilter (file: hasSuffix ".go" file.name) root) (
path.append root "service-mesh"
))
Expand Down

0 comments on commit 0e5edc1

Please sign in to comment.