cli: fix unmarshalling of sev-snp attestation documents in constellation verify (#3171)

Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
Daniel Weiße 2024-06-17 13:38:59 +02:00 committed by GitHub
parent e0f52b4acd
commit 9d99d05826
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 60 additions and 25 deletions

View File

@ -114,6 +114,8 @@ go_library(
"@com_github_google_go_tdx_guest//abi", "@com_github_google_go_tdx_guest//abi",
"@com_github_google_go_tdx_guest//proto/tdx", "@com_github_google_go_tdx_guest//proto/tdx",
"//internal/attestation/azure/tdx", "//internal/attestation/azure/tdx",
"@com_github_google_go_sev_guest//proto/sevsnp",
"@com_github_google_go_tpm_tools//proto/attest",
] + select({ ] + select({
"@io_bazel_rules_go//go/platform:android_amd64": [ "@io_bazel_rules_go//go/platform:android_amd64": [
"@org_golang_x_sys//unix", "@org_golang_x_sys//unix",

View File

@ -38,8 +38,10 @@ import (
"github.com/edgelesssys/constellation/v2/internal/verify" "github.com/edgelesssys/constellation/v2/internal/verify"
"github.com/edgelesssys/constellation/v2/verify/verifyproto" "github.com/edgelesssys/constellation/v2/verify/verifyproto"
"github.com/google/go-sev-guest/proto/sevsnp"
"github.com/google/go-tdx-guest/abi" "github.com/google/go-tdx-guest/abi"
"github.com/google/go-tdx-guest/proto/tdx" "github.com/google/go-tdx-guest/proto/tdx"
"github.com/google/go-tpm-tools/proto/attest"
tpmProto "github.com/google/go-tpm-tools/proto/tpm" tpmProto "github.com/google/go-tpm-tools/proto/tpm"
"github.com/spf13/afero" "github.com/spf13/afero"
"github.com/spf13/cobra" "github.com/spf13/cobra"
@ -191,16 +193,22 @@ func (c *verifyCmd) verify(cmd *cobra.Command, verifyClient verifyClient, config
switch c.flags.output { switch c.flags.output {
case "json": case "json":
attDocOutput, err = formatJSON(cmd.Context(), rawAttestationDoc, attConfig, c.log) attDocOutput, err = formatJSON(cmd.Context(), rawAttestationDoc, attConfig, c.log)
if err != nil {
return fmt.Errorf("printing attestation document: %w", err)
}
case "raw": case "raw":
attDocOutput = fmt.Sprintf("Attestation Document:\n%s\n", rawAttestationDoc) attDocOutput = fmt.Sprintf("Attestation Document:\n%s\n", rawAttestationDoc)
case "": case "":
attDocOutput, err = formatDefault(cmd.Context(), rawAttestationDoc, attConfig, c.log) attDocOutput, err = formatDefault(cmd.Context(), rawAttestationDoc, attConfig, c.log)
if err != nil {
return fmt.Errorf("printing attestation document: %w", err)
}
default: default:
return fmt.Errorf("invalid output value for formatter: %s", c.flags.output) return fmt.Errorf("invalid output value for formatter: %s", c.flags.output)
} }
if err != nil {
return fmt.Errorf("printing attestation document: %w", err)
}
cmd.Println(attDocOutput) cmd.Println(attDocOutput)
cmd.PrintErrln("Verification OK") cmd.PrintErrln("Verification OK")
@ -242,10 +250,10 @@ func (c *verifyCmd) validateEndpointFlag(cmd *cobra.Command, stateFile *state.St
} }
// formatJSON returns the json formatted attestation doc. // formatJSON returns the json formatted attestation doc.
func formatJSON(ctx context.Context, docString string, attestationCfg config.AttestationCfg, log debugLog, func formatJSON(ctx context.Context, docString []byte, attestationCfg config.AttestationCfg, log debugLog,
) (string, error) { ) (string, error) {
var doc vtpm.AttestationDocument doc, err := unmarshalAttDoc(docString, attestationCfg.GetVariant())
if err := json.Unmarshal([]byte(docString), &doc); err != nil { if err != nil {
return "", fmt.Errorf("unmarshalling attestation document: %w", err) return "", fmt.Errorf("unmarshalling attestation document: %w", err)
} }
@ -299,14 +307,14 @@ func tdxFormatJSON(instanceInfoRaw []byte, attestationCfg config.AttestationCfg)
} }
// format returns the formatted attestation doc. // format returns the formatted attestation doc.
func formatDefault(ctx context.Context, docString string, attestationCfg config.AttestationCfg, log debugLog, func formatDefault(ctx context.Context, docString []byte, attestationCfg config.AttestationCfg, log debugLog,
) (string, error) { ) (string, error) {
b := &strings.Builder{} b := &strings.Builder{}
b.WriteString("Attestation Document:\n") b.WriteString("Attestation Document:\n")
var doc vtpm.AttestationDocument doc, err := unmarshalAttDoc(docString, attestationCfg.GetVariant())
if err := json.Unmarshal([]byte(docString), &doc); err != nil { if err != nil {
return "", fmt.Errorf("unmarshal attestation document: %w", err) return "", fmt.Errorf("unmarshalling attestation document: %w", err)
} }
if err := parseQuotes(b, doc.Attestation.Quotes, attestationCfg.GetMeasurements()); err != nil { if err := parseQuotes(b, doc.Attestation.Quotes, attestationCfg.GetMeasurements()); err != nil {
@ -370,11 +378,11 @@ type constellationVerifier struct {
// Verify retrieves an attestation statement from the Constellation and verifies it using the validator. // Verify retrieves an attestation statement from the Constellation and verifies it using the validator.
func (v *constellationVerifier) Verify( func (v *constellationVerifier) Verify(
ctx context.Context, endpoint string, req *verifyproto.GetAttestationRequest, validator atls.Validator, ctx context.Context, endpoint string, req *verifyproto.GetAttestationRequest, validator atls.Validator,
) (string, error) { ) ([]byte, error) {
v.log.Debug(fmt.Sprintf("Dialing endpoint: %q", endpoint)) v.log.Debug(fmt.Sprintf("Dialing endpoint: %q", endpoint))
conn, err := v.dialer.DialInsecure(ctx, endpoint) conn, err := v.dialer.DialInsecure(ctx, endpoint)
if err != nil { if err != nil {
return "", fmt.Errorf("dialing init server: %w", err) return nil, fmt.Errorf("dialing init server: %w", err)
} }
defer conn.Close() defer conn.Close()
@ -383,24 +391,24 @@ func (v *constellationVerifier) Verify(
v.log.Debug("Sending attestation request") v.log.Debug("Sending attestation request")
resp, err := client.GetAttestation(ctx, req) resp, err := client.GetAttestation(ctx, req)
if err != nil { if err != nil {
return "", fmt.Errorf("getting attestation: %w", err) return nil, fmt.Errorf("getting attestation: %w", err)
} }
v.log.Debug("Verifying attestation") v.log.Debug("Verifying attestation")
signedData, err := validator.Validate(ctx, resp.Attestation, req.Nonce) signedData, err := validator.Validate(ctx, resp.Attestation, req.Nonce)
if err != nil { if err != nil {
return "", fmt.Errorf("validating attestation: %w", err) return nil, fmt.Errorf("validating attestation: %w", err)
} }
if !bytes.Equal(signedData, []byte(constants.ConstellationVerifyServiceUserData)) { if !bytes.Equal(signedData, []byte(constants.ConstellationVerifyServiceUserData)) {
return "", errors.New("signed data in attestation does not match expected user data") return nil, errors.New("signed data in attestation does not match expected user data")
} }
return string(resp.Attestation), nil return resp.Attestation, nil
} }
type verifyClient interface { type verifyClient interface {
Verify(ctx context.Context, endpoint string, req *verifyproto.GetAttestationRequest, validator atls.Validator) (string, error) Verify(ctx context.Context, endpoint string, req *verifyproto.GetAttestationRequest, validator atls.Validator) ([]byte, error)
} }
type grpcInsecureDialer interface { type grpcInsecureDialer interface {
@ -515,3 +523,26 @@ func decodeMeasurement(encoded string) ([]byte, error) {
} }
return decoded, nil return decoded, nil
} }
func unmarshalAttDoc(attDocJSON []byte, attestationVariant variant.Variant) (vtpm.AttestationDocument, error) {
attDoc := vtpm.AttestationDocument{
Attestation: &attest.Attestation{},
}
// Explicitly initialize this struct, as TeeAttestation
// is a "oneof" protobuf field, which needs an explicit
// type to be set to be unmarshaled correctly.
switch attestationVariant {
case variant.AzureTDX{}:
attDoc.Attestation.TeeAttestation = &attest.Attestation_TdxAttestation{
TdxAttestation: &tdx.QuoteV4{},
}
default:
attDoc.Attestation.TeeAttestation = &attest.Attestation_SevSnpAttestation{
SevSnpAttestation: &sevsnp.Attestation{},
}
}
err := json.Unmarshal(attDocJSON, &attDoc)
return attDoc, err
}

View File

@ -208,18 +208,20 @@ func TestVerify(t *testing.T) {
func TestFormatDefault(t *testing.T) { func TestFormatDefault(t *testing.T) {
testCases := map[string]struct { testCases := map[string]struct {
doc string doc []byte
attCfg config.AttestationCfg
wantErr bool wantErr bool
}{ }{
"invalid doc": { "invalid doc": {
doc: "invalid", doc: []byte("invalid"),
attCfg: &config.AzureSEVSNP{},
wantErr: true, wantErr: true,
}, },
} }
for name, tc := range testCases { for name, tc := range testCases {
t.Run(name, func(t *testing.T) { t.Run(name, func(t *testing.T) {
_, err := formatDefault(context.Background(), tc.doc, nil, logger.NewTest(t)) _, err := formatDefault(context.Background(), tc.doc, tc.attCfg, logger.NewTest(t))
if tc.wantErr { if tc.wantErr {
assert.Error(t, err) assert.Error(t, err)
} else { } else {
@ -313,9 +315,9 @@ type stubVerifyClient struct {
endpoint string endpoint string
} }
func (c *stubVerifyClient) Verify(_ context.Context, endpoint string, _ *verifyproto.GetAttestationRequest, _ atls.Validator) (string, error) { func (c *stubVerifyClient) Verify(_ context.Context, endpoint string, _ *verifyproto.GetAttestationRequest, _ atls.Validator) ([]byte, error) {
c.endpoint = endpoint c.endpoint = endpoint
return "", c.verifyErr return nil, c.verifyErr
} }
type stubVerifyAPI struct { type stubVerifyAPI struct {

2
go.mod
View File

@ -79,7 +79,7 @@ require (
github.com/golang-jwt/jwt/v5 v5.2.1 github.com/golang-jwt/jwt/v5 v5.2.1
github.com/google/go-sev-guest v0.11.1 github.com/google/go-sev-guest v0.11.1
github.com/google/go-tdx-guest v0.3.1 github.com/google/go-tdx-guest v0.3.1
github.com/google/go-tpm v0.9.1-0.20240510201744-5c2f0887e003 github.com/google/go-tpm v0.9.1
github.com/google/go-tpm-tools v0.4.4 github.com/google/go-tpm-tools v0.4.4
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/googleapis/gax-go/v2 v2.12.4 github.com/googleapis/gax-go/v2 v2.12.4

4
go.sum
View File

@ -420,8 +420,8 @@ github.com/google/go-sev-guest v0.11.1 h1:gnww4U8fHV5DCPz4gykr1s8SEX1fFNcxCBy+vv
github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w= github.com/google/go-sev-guest v0.11.1/go.mod h1:qBOfb+JmgsUI3aUyzQoGC13Kpp9zwLeWvuyXmA9q77w=
github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw= github.com/google/go-tdx-guest v0.3.1 h1:gl0KvjdsD4RrJzyLefDOvFOUH3NAJri/3qvaL5m83Iw=
github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE= github.com/google/go-tdx-guest v0.3.1/go.mod h1:/rc3d7rnPykOPuY8U9saMyEps0PZDThLk/RygXm04nE=
github.com/google/go-tpm v0.9.1-0.20240510201744-5c2f0887e003 h1:gfGQAIxsEEAuYuFvjCGpDnTwisMJOz+rUfJMkk4yTmc= github.com/google/go-tpm v0.9.1 h1:0pGc4X//bAlmZzMKf8iz6IsDo1nYTbYJ6FZN/rg4zdM=
github.com/google/go-tpm v0.9.1-0.20240510201744-5c2f0887e003/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY= github.com/google/go-tpm v0.9.1/go.mod h1:h9jEsEECg7gtLis0upRBQU+GhYVH6jMjrFxI8u6bVUY=
github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98= github.com/google/go-tpm-tools v0.4.4 h1:oiQfAIkc6xTy9Fl5NKTeTJkBTlXdHsxAofmQyxBKY98=
github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY= github.com/google/go-tpm-tools v0.4.4/go.mod h1:T8jXkp2s+eltnCDIsXR84/MTcVU9Ja7bh3Mit0pa4AY=
github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus= github.com/google/go-tspi v0.3.0 h1:ADtq8RKfP+jrTyIWIZDIYcKOMecRqNJFOew2IT0Inus=