Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions pcs/pcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ var (
OidPCEID = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 3})
// OidFMSPC is the x509v3 extension for PCK certificate's SGX Extensions FMSPC value.
OidFMSPC = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 4})
// OidSGXType is the x509v3 extension for PCK certificate's SGX Extensions SGX Type value.
OidSGXType = asn1.ObjectIdentifier([]int{1, 2, 840, 113741, 1, 13, 1, 5})

// ErrPckExtInvalid error returned when parsing PCK certificate's extension returns leftover bytes
ErrPckExtInvalid = errors.New("unexpected leftover bytes for PCK certificate's extension")
Expand Down Expand Up @@ -176,14 +178,27 @@ type PckCertTCB struct {
}

// PckExtensions represents the information stored in the x509 extensions of a PCK certificate which
// will be required for verification
// will be required for verification/validation
type PckExtensions struct {
PPID string
TCB PckCertTCB
PCEID string
FMSPC string
PPID string
TCB PckCertTCB
PCEID string
FMSPC string
SGXType SGXType
}

// SGXType represents the type of the platform for which the PCK certificate was created
type SGXType int

const (
// SGXTypeStandard represents a standard (non-scalable) platform.
SGXTypeStandard SGXType = iota
// SGXTypeScalable represents a scalable platform with logical integrity protection.
SGXTypeScalable
// SGXTypeScalableWithIntegrity represents a scalable platform with cryptographic integrity protection.
SGXTypeScalableWithIntegrity
)

// HexBytes struct contains hex decoded string to bytes value
type HexBytes struct {
Bytes []byte
Expand Down Expand Up @@ -394,6 +409,15 @@ func extractAsn1OctetStringExtension(name string, extension asn1.RawValue, size
return hex.EncodeToString(val), nil
}

// attributeTypeAndEnumerated is a specialized version of
// pkix.AttributeTypeAndValue to work around a bug where Go's ASN.1 parser
// fails to unmarshal `asn1.Enumerated` values into the `any` field in
// `AttributeTypeAndValue`: https://github.com/golang/go/pull/69727
type attributeTypeAndEnumerated struct {
Type asn1.ObjectIdentifier
Value asn1.Enumerated
}

func extractSgxExtensions(extensions []asn1.RawValue) (*PckExtensions, error) {
pckExtension := &PckExtensions{}
if len(extensions) < sgxExtensionMinSize {
Expand Down Expand Up @@ -434,6 +458,14 @@ func extractSgxExtensions(extensions []asn1.RawValue) (*PckExtensions, error) {
return nil, err
}
}
if sExtension.Type.Equal(OidSGXType) {
var sExtension attributeTypeAndEnumerated
_, err := asn1.Unmarshal(ext.FullBytes, &sExtension)
if err != nil {
return nil, fmt.Errorf("could not parse SGX extension's in PCK certificate: %v", err)
}
pckExtension.SGXType = SGXType(sExtension.Value)
}
}
return pckExtension, nil
}
Expand All @@ -448,7 +480,7 @@ func findMatchingExtension(extns []pkix.Extension, oid asn1.ObjectIdentifier) (*
}

// PckCertificateExtensions returns only those x509v3 extensions from the PCK certificate into a
// struct type which will be required in verification purpose.
// struct type which will be required for verification or validation purposes.
func PckCertificateExtensions(cert *x509.Certificate) (*PckExtensions, error) {
if len(cert.Extensions) != pckCertExtensionSize {
return nil, fmt.Errorf("PCK certificate extensions length found %d. Expected %d", len(cert.Extensions), pckCertExtensionSize)
Expand Down
12 changes: 12 additions & 0 deletions proto/checkconfig.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ message Policy {
// TDQuoteBodyPolicy is representation of TdQuoteBody of an attestation quote
// validation policy.
TDQuoteBodyPolicy td_quote_body_policy = 2; // should be 528 bytes

PCKPolicy pck_policy = 3;
}

message HeaderPolicy {
Expand All @@ -55,6 +57,16 @@ message TDQuoteBodyPolicy {
bool enable_td_debug_check = 12; // if true, check that the DEBUG bit is 0 in TDAttributes.
}

message PCKPolicy {
optional SGXType sgx_type = 1;
}

enum SGXType {
Standard = 0;
Scalable = 1;
ScalableWithIntegrity = 2;
}

// RootOfTrust represents configuration for which hardware root of trust
// certificates to use for verifying attestation quote.
message RootOfTrust {
Expand Down
296 changes: 216 additions & 80 deletions proto/checkconfig/checkconfig.pb.go

Large diffs are not rendered by default.

18 changes: 16 additions & 2 deletions tools/check/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ var (
minqesvn = flag.String("minimum_qe_svn", "", "The minimum acceptable value for QE_SVN field.")
minpcesvn = flag.String("minimum_pce_svn", "", "The minimum acceptable value for PCE_SVN field.")

sgxtype = flag.String("sgx_type", "", "An acceptable value for SGXType field.")

// Optional Bool
checkcrl = flag.String("check_crl", "", "Download and check the CRL for revoked certificates. -get_collateral must be true.")
getcollateral = flag.String("get_collateral", "", "If true, then permitted to download necessary collaterals for additional checks.")
Expand All @@ -119,7 +121,7 @@ var (
// Assign the values of the flags to the corresponding proto fields
config = &ccpb.Config{
RootOfTrust: &ccpb.RootOfTrust{},
Policy: &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}},
Policy: &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}, PckPolicy: &ccpb.PCKPolicy{}},
}
)

Expand Down Expand Up @@ -232,7 +234,7 @@ func parseConfig(path string) error {
config.RootOfTrust = &ccpb.RootOfTrust{}
}
if config.Policy == nil {
config.Policy = &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}}
config.Policy = &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}, PckPolicy: &ccpb.PCKPolicy{}}
}
return nil
}
Expand Down Expand Up @@ -355,6 +357,17 @@ func populateConfig() error {
}
return nil
}
setSGXType := func(dest **ccpb.SGXType, flag string) error {
if flag != "" {
val, ok := ccpb.SGXType_value[flag]
if !ok {
return fmt.Errorf("invalid -sgx_type=%s: expected one of Standard, Scalable, or ScalableWithIntegrity", flag)
}
sgxType := ccpb.SGXType(val)
*dest = &sgxType
}
return nil
}

setNonNil(&policy.HeaderPolicy.QeVendorId, *qevendorid)
setNonNil(&policy.TdQuoteBodyPolicy.MinimumTeeTcbSvn, *minteetcbsvn)
Expand All @@ -370,6 +383,7 @@ func populateConfig() error {
return multierr.Combine(
setUint32(&policy.HeaderPolicy.MinimumQeSvn, "minimum_qe_svn", *minqesvn, defaultMinQeSvn),
setUint32(&policy.HeaderPolicy.MinimumPceSvn, "minimum_pce_svn", *minpcesvn, defaultMinPceSvn),
setSGXType(&policy.PckPolicy.SgxType, *sgxtype),
setRtmrs(&policy.TdQuoteBodyPolicy.Rtmrs, *rtmrs),
)
}
Expand Down
30 changes: 26 additions & 4 deletions tools/check/check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,11 @@ func setField(p *ccpb.Policy, policy string, name string, value any) {
r := s.ProtoReflect()
ty := r.Descriptor()
r.Set(ty.Fields().ByName(protoreflect.Name(name)), protoreflect.ValueOf(value))
} else if policy == "pck_policy" {
s := p.PckPolicy
r := s.ProtoReflect()
ty := r.Descriptor()
r.Set(ty.Fields().ByName(protoreflect.Name(name)), protoreflect.ValueOf(value))
}
}

Expand All @@ -115,6 +120,13 @@ func uint32setter(name string, policy string) setterFn {
}
}

func enumSetter(name string, policy string, values map[string]int32) setterFn {
return func(p *ccpb.Policy, value string, _ *testing.T) bool {
setField(p, policy, name, protoreflect.EnumNumber(values[value]))
return false
}
}

func testCases() []testCase {
return []testCase{
{
Expand Down Expand Up @@ -200,6 +212,16 @@ func testCases() []testCase {
bad: []string{"6c62dec1b8191749a31dab490be532a35944dea47caef1f980863993d9899545eb7406a38d1eed313b987a467dacead6f0c87a6d766c66f6f29f8acb281f2213"},
setter: bytesSetter("report_data", "td_quote_body_policy"),
},
{
flag: "sgx_type",
good: "Scalable",
bad: []string{
"Standard",
"ScalableWithIntegrity",
"non-existing type",
},
setter: enumSetter("sgx_type", "pck_policy", ccpb.SGXType_value),
},
}
}

Expand Down Expand Up @@ -288,7 +310,7 @@ func TestRtmrs(t *testing.T) {
func TestCheckGoodFields(t *testing.T) {
for _, tc := range testCases() {
t.Run(tc.flag, func(t *testing.T) {
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}}
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}, PckPolicy: &ccpb.PCKPolicy{}}
if tc.setter(p, tc.good, t) {
t.Fatal("unexpected parse failure")
}
Expand All @@ -306,7 +328,7 @@ func TestCheckBadFields(t *testing.T) {
for _, tc := range testCases() {
for i, bad := range tc.bad {
t.Run(fmt.Sprintf("%s_bad[%d]", tc.flag, i+1), func(t *testing.T) {
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}}
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}, PckPolicy: &ccpb.PCKPolicy{}}
if tc.setter(p, bad, t) {
return
}
Expand All @@ -325,7 +347,7 @@ func TestCheckGoodFlagOverridesBadField(t *testing.T) {
for _, tc := range testCases() {
for i, bad := range tc.bad {
t.Run(fmt.Sprintf("%s_bad[%d]", tc.flag, i+1), func(t *testing.T) {
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}}
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}, PckPolicy: &ccpb.PCKPolicy{}}
if tc.setter(p, bad, t) {
return
}
Expand All @@ -344,7 +366,7 @@ func TestCheckBadFlagOverridesGoodField(t *testing.T) {
for _, tc := range testCases() {
for i, bad := range tc.bad {
t.Run(fmt.Sprintf("%s_bad[%d]", tc.flag, i+1), func(t *testing.T) {
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}}
p := &ccpb.Policy{HeaderPolicy: &ccpb.HeaderPolicy{}, TdQuoteBodyPolicy: &ccpb.TDQuoteBodyPolicy{}, PckPolicy: &ccpb.PCKPolicy{}}
if tc.setter(p, tc.good, t) {
t.Fatal("unexpected parse failure")
}
Expand Down
42 changes: 42 additions & 0 deletions validate/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,15 @@ package validate

import (
"bytes"
"crypto/x509"
"encoding/binary"
"encoding/hex"
"encoding/pem"
"errors"
"fmt"

"github.com/google/go-tdx-guest/abi"
"github.com/google/go-tdx-guest/pcs"
ccpb "github.com/google/go-tdx-guest/proto/checkconfig"
pb "github.com/google/go-tdx-guest/proto/tdx"
vr "github.com/google/go-tdx-guest/verify"
Expand Down Expand Up @@ -51,6 +55,7 @@ const (
type Options struct {
HeaderOptions HeaderOptions
TdQuoteBodyOptions TdQuoteBodyOptions
PCKOptions PCKOptions
}

// HeaderOptions represents validation options for a TDX attestation Quote Header.
Expand Down Expand Up @@ -92,6 +97,12 @@ type TdQuoteBodyOptions struct {
EnableTdDebugCheck bool
}

// PCKOptions represents validation options for the PCK certificate in side a TDX attestation.
type PCKOptions struct {
// SgxType is the expected SGXType. Not checked if nil.
SgxType *pcs.SGXType
}

func lengthCheck(name string, length int, value []byte) error {
if value != nil && len(value) != length {
return fmt.Errorf("option %q length is %d. Want %d", name, len(value), length)
Expand Down Expand Up @@ -169,6 +180,10 @@ func PolicyToOptions(policy *ccpb.Policy) (*Options, error) {
},
}

if policy.PckPolicy != nil && policy.PckPolicy.SgxType != nil {
value := pcs.SGXType(*policy.PckPolicy.SgxType)
opts.PCKOptions.SgxType = &value
}
if err := checkOptionsLengths(opts); err != nil {
return nil, err
}
Expand Down Expand Up @@ -328,6 +343,32 @@ func validateTdAttributes(value []byte, fixed1, fixed0 uint64, enableTdDebugChec
return nil
}

func validatePck(quote *pb.QuoteV4, opts *PCKOptions) error {
// Extract the PCK
certChainBytes := quote.GetSignedData().GetCertificationData().GetQeReportCertificationData().GetPckCertificateChainData().GetPckCertChain()
if certChainBytes == nil {
return errors.New("PCK certificate chain is empty")
}
pck, rem := pem.Decode(certChainBytes)
if pck == nil || len(rem) == 0 || pck.Type != "CERTIFICATE" {
return errors.New("incomplete PCK Certificate chain found, should contain 3 concatenated PEM-formatted 'CERTIFICATE'-type block (PCK Leaf Cert||Intermediate CA Cert||Root CA Cert)")
}
pckCert, err := x509.ParseCertificate(pck.Bytes)
if err != nil {
return fmt.Errorf("could not interpret PCK leaf certificate DER bytes: %v", err)
}

// Validate the PCK.
exts, err := pcs.PckCertificateExtensions(pckCert)
if err != nil {
return fmt.Errorf("could not get PCK certificate extensions: %v", err)
}
if opts.SgxType != nil && *opts.SgxType != exts.SGXType {
return fmt.Errorf("PCK extension SGXType is %d. Expect %d", *opts.SgxType, exts.SGXType)
}
return nil
}

// TdxQuote validates fields of the protobuf representation of an attestation Quote
// against expectations depending on supported quote formats - QuoteV4.
// Does not check the attestation certificates or signature.
Expand Down Expand Up @@ -355,6 +396,7 @@ func tdxQuoteV4(quote *pb.QuoteV4, options *Options) error {
minVersionCheck(quote, options),
validateXfam(quote.GetTdQuoteBody().GetXfam(), xfamFixed1, xfamFixed0),
validateTdAttributes(quote.GetTdQuoteBody().GetTdAttributes(), tdAttributesFixed1, tdAttributesFixed0, options.TdQuoteBodyOptions.EnableTdDebugCheck),
validatePck(quote, &options.PCKOptions),
)
}

Expand Down
13 changes: 13 additions & 0 deletions validate/validate_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
"strings"
"testing"

"github.com/google/go-tdx-guest/pcs"
pb "github.com/google/go-tdx-guest/proto/tdx"
vr "github.com/google/go-tdx-guest/verify"

Expand Down Expand Up @@ -73,6 +74,7 @@ func TestTdxQuote(t *testing.T) {
0x59, 0x44, 0xde, 0xa4, 0x7c, 0xae, 0xf1, 0xf9, 0x80, 0x86, 0x39, 0x93, 0xd9, 0x89, 0x95, 0x45,
0xeb, 0x74, 0x6, 0xa3, 0x8d, 0x1e, 0xed, 0x31, 0x3b, 0x98, 0x7a, 0x46, 0x7d, 0xac, 0xea, 0xd6,
0xf0, 0xc8, 0x7a, 0x6d, 0x76, 0x6c, 0x66, 0xf6, 0xf2, 0x9f, 0x8a, 0xcb, 0x28, 0x1f, 0x11, 0x13}
sgxType := pcs.SGXTypeScalable

mknonce := func(front []byte) []byte {
result := make([]byte, 64)
Expand Down Expand Up @@ -140,6 +142,9 @@ func TestTdxQuote(t *testing.T) {
ReportData: reportData,
EnableTdDebugCheck: true,
},
PCKOptions: PCKOptions{
SgxType: &sgxType,
},
},
},
{
Expand Down Expand Up @@ -295,6 +300,14 @@ func TestTdxQuote(t *testing.T) {
},
wantErr: "TD_ATTRIBUTES DEBUG bit is set, but debug is not allowed",
},
{
name: "Test incorrect SGXType",
quote: quote12345,
opts: &Options{
PCKOptions: PCKOptions{SgxType: new(pcs.SGXType)},
},
wantErr: "PCK extension SGXType",
},
}

for _, tc := range tests {
Expand Down
1 change: 1 addition & 0 deletions verify/verify_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ func TestPckCertificateExtensions(t *testing.T) {
pckExt.PPID = hex.EncodeToString(ppidBytes)
pckExt.FMSPC = hex.EncodeToString(fmspcBytes)
pckExt.PCEID = hex.EncodeToString(pceIDBytes)
pckExt.SGXType = pcs.SGXTypeScalable
pckExtTcb := &pcs.PckCertTCB{
PCESvn: 11,
CPUSvn: []byte{3, 3, 2, 2, 2, 1, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0},
Expand Down
Loading