Skip to content

Commit

Permalink
manifest: enforce ProductName in SNP reference values
Browse files Browse the repository at this point in the history
We do this by only adding the product's root certs to the verification
options.
  • Loading branch information
Freax13 committed Aug 19, 2024
1 parent cb4e329 commit 2935138
Show file tree
Hide file tree
Showing 10 changed files with 91 additions and 80 deletions.
4 changes: 2 additions & 2 deletions cli/cmd/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,14 +90,14 @@ func validatorsFromManifest(m *manifest.Manifest, log *slog.Logger, hostData []b
kdsCache := fsstore.New(kdsDir, log.WithGroup("kds-cache"))
kdsGetter := snp.NewCachedHTTPSGetter(kdsCache, snp.NeverGCTicker, log.WithGroup("kds-getter"))

opts, err := m.SNPValidateOpts()
opts, err := m.SNPValidateOpts(kdsGetter)
if err != nil {
return nil, fmt.Errorf("getting SNP validate options: %w", err)
}

var validators []atls.Validator
for _, opt := range opts {
validators = append(validators, snp.NewValidator(opt, []manifest.HexString{manifest.NewHexString(hostData)}, kdsGetter,
validators = append(validators, snp.NewValidator(opt.VerifyOpts, opt.ValidateOpts, []manifest.HexString{manifest.NewHexString(hostData)},
logger.NewWithAttrs(logger.NewNamed(log, "validator"), map[string]string{"tee-type": "snp"}),
))
}
Expand Down
2 changes: 1 addition & 1 deletion coordinator/internal/authority/authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ func TestSNPValidateOpts(t *testing.T) {
_, err := a.SetManifest(context.Background(), req)
require.NoError(err)

gens, err := a.state.Load().Manifest.SNPValidateOpts()
gens, err := a.state.Load().Manifest.SNPValidateOpts(nil)
require.NoError(err)
require.NotNil(gens)
}
Expand Down
4 changes: 2 additions & 2 deletions coordinator/internal/authority/credentials.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.A

authInfo := AuthInfo{State: state}

opts, err := state.Manifest.SNPValidateOpts()
opts, err := state.Manifest.SNPValidateOpts(c.kdsGetter)
if err != nil {
return nil, nil, fmt.Errorf("generating SNP validation options: %w", err)
}
Expand All @@ -85,7 +85,7 @@ func (c *Credentials) ServerHandshake(rawConn net.Conn) (net.Conn, credentials.A

var validators []atls.Validator
for _, opt := range opts {
validator := snp.NewValidatorWithCallbacks(opt, allowedHostDataEntries, c.kdsGetter,
validator := snp.NewValidatorWithCallbacks(opt.VerifyOpts, opt.ValidateOpts, allowedHostDataEntries,
logger.NewWithAttrs(logger.NewNamed(c.logger, "validator"), map[string]string{"tee-type": "snp"}),
&authInfo)
validators = append(validators, validator)
Expand Down
60 changes: 11 additions & 49 deletions internal/attestation/snp/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ package snp

import (
"context"
_ "embed"
"encoding/asn1"
"encoding/hex"
"fmt"
Expand All @@ -19,16 +18,15 @@ import (
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-sev-guest/validate"
"github.com/google/go-sev-guest/verify"
"github.com/google/go-sev-guest/verify/trust"
"google.golang.org/protobuf/proto"
)

// Validator validates attestation statements.
type Validator struct {
opts *validate.Options
verifyOpts *verify.Options
validateOpts *validate.Options
allowedHostDataEntries []manifest.HexString // Allowed host data entries in the report. If any of these is present, the report is considered valid.
callbackers []validateCallbacker
kdsGetter trust.HTTPSGetter
logger *slog.Logger
}

Expand All @@ -38,26 +36,24 @@ type validateCallbacker interface {
}

// NewValidator returns a new Validator.
func NewValidator(opts *validate.Options, allowedHostDataEntries []manifest.HexString,
kdsGetter trust.HTTPSGetter, log *slog.Logger,
) *Validator {
func NewValidator(VerifyOpts *verify.Options, ValidateOpts *validate.Options, allowedHostDataEntries []manifest.HexString, log *slog.Logger) *Validator {
return &Validator{
opts: opts,
verifyOpts: VerifyOpts,
validateOpts: ValidateOpts,
allowedHostDataEntries: allowedHostDataEntries,
kdsGetter: kdsGetter,
logger: log,
}
}

// NewValidatorWithCallbacks returns a new Validator with callbacks.
func NewValidatorWithCallbacks(opts *validate.Options, allowedHostDataEntries []manifest.HexString, kdsGetter trust.HTTPSGetter,
func NewValidatorWithCallbacks(VerifyOpts *verify.Options, ValidateOpts *validate.Options, allowedHostDataEntries []manifest.HexString,
log *slog.Logger, callbacks ...validateCallbacker,
) *Validator {
return &Validator{
opts: opts,
verifyOpts: VerifyOpts,
validateOpts: ValidateOpts,
allowedHostDataEntries: allowedHostDataEntries,
callbackers: callbacks,
kdsGetter: kdsGetter,
logger: log,
}
}
Expand Down Expand Up @@ -87,25 +83,18 @@ func (v *Validator) Validate(ctx context.Context, attDocRaw []byte, nonce []byte
}
v.logger.Info("Report decoded", "reportRaw", hex.EncodeToString(reportRaw))

verifyOpts := verify.DefaultOptions()
// TODO(Freax13): We won't need this once https://github.com/google/go-sev-guest/pull/127 is merged.
verifyOpts.TrustedRoots = trustedRoots()
verifyOpts.Product = attestation.Product
verifyOpts.CheckRevocations = true
verifyOpts.Getter = v.kdsGetter

// Report signature verification.

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

// Validate the report data.

reportDataExpected := reportdata.Construct(peerPublicKey, nonce)
v.opts.ReportData = reportDataExpected[:]
if err := validate.SnpAttestation(attestation, v.opts); err != nil {
v.validateOpts.ReportData = reportDataExpected[:]
if err := validate.SnpAttestation(attestation, v.validateOpts); err != nil {
return fmt.Errorf("validating report claims: %w", err)
}
v.logger.Info("Successfully validated report data")
Expand All @@ -131,30 +120,3 @@ func (v *Validator) Validate(ctx context.Context, attDocRaw []byte, nonce []byte
v.logger.Info("Validate finished successfully")
return nil
}

var (
// source: https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
//go:embed Milan.pem
askArkMilanVcekBytes []byte
// source: https://kdsintf.amd.com/vcek/v1/Genoa/cert_chain
//go:embed Genoa.pem
askArkGenoaVcekBytes []byte
)

func trustedRoots() map[string][]*trust.AMDRootCerts {
trustedRoots := make(map[string][]*trust.AMDRootCerts)

milanCerts := trust.AMDRootCertsProduct("Milan")
if err := milanCerts.FromKDSCertBytes(askArkMilanVcekBytes); err != nil {
panic(fmt.Errorf("failed to parse cert: %w", err))
}
trustedRoots["Milan"] = []*trust.AMDRootCerts{milanCerts}

genoaCerts := trust.AMDRootCertsProduct("Genoa")
if err := genoaCerts.FromKDSCertBytes(askArkGenoaVcekBytes); err != nil {
panic(fmt.Errorf("failed to parse cert: %w", err))
}
trustedRoots["Genoa"] = []*trust.AMDRootCerts{genoaCerts}

return trustedRoots
}
16 changes: 0 additions & 16 deletions internal/attestation/snp/validator_test.go

This file was deleted.

File renamed without changes.
File renamed without changes.
61 changes: 57 additions & 4 deletions internal/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,16 @@ package manifest

import (
"crypto/sha256"
_ "embed"
"encoding/base64"
"errors"
"fmt"

"github.com/google/go-sev-guest/abi"
"github.com/google/go-sev-guest/kds"
"github.com/google/go-sev-guest/validate"
"github.com/google/go-sev-guest/verify"
"github.com/google/go-sev-guest/verify/trust"
)

// Manifest is the Coordinator manifest and contains the reference values of the deployment.
Expand Down Expand Up @@ -101,6 +104,7 @@ func (r SNPReferenceValues) Validate() error {

switch r.ProductName {
case Milan, Genoa:
// These are valid. We don't need to report an error.
default:
return fmt.Errorf("unknown product name: %s", r.ProductName)
}
Expand Down Expand Up @@ -152,9 +156,16 @@ func (m *Manifest) Validate() error {

// TODO(msanft): add generic validation interface for other attestation types.

// ValidatorOptions contains the verification and validation options to be used
// by a Validator.
type ValidatorOptions struct {
VerifyOpts *verify.Options
ValidateOpts *validate.Options
}

// SNPValidateOpts returns validate options generators populated with the manifest's
// SNP reference values and trusted measurement for the given runtime.
func (m *Manifest) SNPValidateOpts() ([]*validate.Options, error) {
func (m *Manifest) SNPValidateOpts(kdsGetter trust.HTTPSGetter) ([]ValidatorOptions, error) {
if len(m.ReferenceValues.SNP) == 0 {
return nil, errors.New("reference values cannot be empty")
}
Expand All @@ -163,7 +174,7 @@ func (m *Manifest) SNPValidateOpts() ([]*validate.Options, error) {
return nil, fmt.Errorf("validating manifest: %w", err)
}

var out []*validate.Options
var out []ValidatorOptions
for _, refVal := range m.ReferenceValues.SNP {
if len(refVal.TrustedMeasurement) == 0 {
return nil, errors.New("trusted measurement cannot be empty")
Expand All @@ -174,7 +185,15 @@ func (m *Manifest) SNPValidateOpts() ([]*validate.Options, error) {
return nil, fmt.Errorf("failed to convert TrustedMeasurement from manifest to byte slices: %w", err)
}

out = append(out, &validate.Options{
verifyOpts := verify.DefaultOptions()
verifyOpts.TrustedRoots, err = trustedRoots(refVal.ProductName)
if err != nil {
return nil, fmt.Errorf("determine trusted roots: %w", err)
}
verifyOpts.CheckRevocations = true
verifyOpts.Getter = kdsGetter

validateOpts := validate.Options{
Measurement: trustedMeasurement,
GuestPolicy: abi.SnpPolicy{
Debug: false,
Expand All @@ -194,7 +213,9 @@ func (m *Manifest) SNPValidateOpts() ([]*validate.Options, error) {
UcodeSpl: refVal.MinimumTCB.MicrocodeVersion.UInt8(),
},
PermitProvisionalFirmware: true,
})
}

out = append(out, ValidatorOptions{VerifyOpts: verifyOpts, ValidateOpts: &validateOpts})
}

if len(out) == 0 {
Expand All @@ -203,3 +224,35 @@ func (m *Manifest) SNPValidateOpts() ([]*validate.Options, error) {

return out, nil
}

var (
// source: https://kdsintf.amd.com/vcek/v1/Milan/cert_chain
//go:embed Milan.pem
askArkMilanVcekBytes []byte
// source: https://kdsintf.amd.com/vcek/v1/Genoa/cert_chain
//go:embed Genoa.pem
askArkGenoaVcekBytes []byte
)

func trustedRoots(productName ProductName) (map[string][]*trust.AMDRootCerts, error) {
trustedRoots := make(map[string][]*trust.AMDRootCerts)

switch productName {
case Milan:
milanCerts := trust.AMDRootCertsProduct("Milan")
if err := milanCerts.FromKDSCertBytes(askArkMilanVcekBytes); err != nil {
panic(fmt.Errorf("failed to parse cert: %w", err))
}
trustedRoots["Milan"] = []*trust.AMDRootCerts{milanCerts}
case Genoa:
genoaCerts := trust.AMDRootCertsProduct("Genoa")
if err := genoaCerts.FromKDSCertBytes(askArkGenoaVcekBytes); err != nil {
panic(fmt.Errorf("failed to parse cert: %w", err))
}
trustedRoots["Genoa"] = []*trust.AMDRootCerts{genoaCerts}
default:
return nil, fmt.Errorf("unknown product name: %s", productName)
}

return trustedRoots, nil
}
20 changes: 16 additions & 4 deletions internal/manifest/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ func TestAKSValidateOpts(t *testing.T) {
m, err := Default(platforms.AKSCloudHypervisorSNP)
require.NoError(err)

opts, err := m.SNPValidateOpts()
opts, err := m.SNPValidateOpts(nil)
require.NoError(err)
require.Len(opts, 1)

Expand All @@ -145,14 +145,26 @@ func TestAKSValidateOpts(t *testing.T) {
trustedMeasurement, err := m.ReferenceValues.SNP[0].TrustedMeasurement.Bytes()
assert.NoError(err)

assert.Equal(trustedMeasurement, opts[0].Measurement)
assert.Equal(trustedMeasurement, opts[0].ValidateOpts.Measurement)

tcbParts := kds.TCBParts{
BlSpl: tcb.BootloaderVersion.UInt8(),
TeeSpl: tcb.TEEVersion.UInt8(),
SnpSpl: tcb.SNPVersion.UInt8(),
UcodeSpl: tcb.MicrocodeVersion.UInt8(),
}
assert.Equal(tcbParts, opts[0].MinimumTCB)
assert.Equal(tcbParts, opts[0].MinimumLaunchTCB)
assert.Equal(tcbParts, opts[0].ValidateOpts.MinimumTCB)
assert.Equal(tcbParts, opts[0].ValidateOpts.MinimumLaunchTCB)
}

func TestTrustedRoots(t *testing.T) {
roots, err := trustedRoots(Milan)
assert.NoError(t, err)
assert.Contains(t, roots, "Milan")
assert.NotContains(t, roots, "Genoa")

roots, err = trustedRoots(Genoa)
assert.NoError(t, err)
assert.NotContains(t, roots, "Milan")
assert.Contains(t, roots, "Genoa")
}
4 changes: 2 additions & 2 deletions packages/by-name/contrast/package.nix
Original file line number Diff line number Diff line change
Expand Up @@ -141,8 +141,8 @@ buildGoModule rec {
(path.append root "go.sum")
(path.append root "cli/cmd/assets/image-replacements.txt")
(path.append root "cli/genpolicy/assets/allow-all.rego")
(path.append root "internal/attestation/snp/Milan.pem")
(path.append root "internal/attestation/snp/Genoa.pem")
(path.append root "internal/manifest/Milan.pem")
(path.append root "internal/manifest/Genoa.pem")
(path.append root "nodeinstaller")
(fileset.difference (fileset.fileFilter (file: hasSuffix ".go" file.name) root) (
path.append root "service-mesh"
Expand Down

0 comments on commit 2935138

Please sign in to comment.