Skip to content

Commit

Permalink
manifest: validate manifest fields
Browse files Browse the repository at this point in the history
  • Loading branch information
davidweisse committed Jun 25, 2024
1 parent 2b7c9e4 commit caeaf76
Show file tree
Hide file tree
Showing 5 changed files with 115 additions and 47 deletions.
3 changes: 3 additions & 0 deletions cli/cmd/generate.go
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,9 @@ func runGenerate(cmd *cobra.Command, args []string) error {
return fmt.Errorf("failed to unmarshal manifest: %w", err)
}
manifest.Policies = policyMap
if err := manifest.Validate(); err != nil {
return fmt.Errorf("validating manifest: %w", err)
}

if flags.disableUpdates {
manifest.WorkloadOwnerKeyDigests = nil
Expand Down
3 changes: 3 additions & 0 deletions cli/cmd/set.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,9 @@ func runSet(cmd *cobra.Command, args []string) error {
if err := json.Unmarshal(manifestBytes, &m); err != nil {
return fmt.Errorf("failed to unmarshal manifest: %w", err)
}
if err := m.Validate(); err != nil {
return fmt.Errorf("validating manifest: %w", err)
}

workloadOwnerKey, err := loadWorkloadOwnerKey(flags.workloadOwnerKeyPath, m, log)
if err != nil {
Expand Down
3 changes: 3 additions & 0 deletions cli/cmd/verify.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,9 @@ func runVerify(cmd *cobra.Command, _ []string) error {
if err := json.Unmarshal(manifestBytes, &m); err != nil {
return fmt.Errorf("failed to unmarshal manifest: %w", err)
}
if err := m.Validate(); err != nil {
return fmt.Errorf("validating manifest: %w", err)
}

kdsDir, err := cachedir("kds")
if err != nil {
Expand Down
69 changes: 48 additions & 21 deletions internal/manifest/manifest.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,21 +126,61 @@ func (p Policy) Hash() HexString {
return NewHexString(hashBytes[:])
}

// Validate checks the validity of all fields in the reference values.
func (r ReferenceValues) Validate() error {
if r.SNP.MinimumTCB.BootloaderVersion == nil {
return fmt.Errorf("field BootloaderVersion in manifest cannot be empty")
} else if r.SNP.MinimumTCB.TEEVersion == nil {
return fmt.Errorf("field TEEVersion in manifest cannot be empty")
} else if r.SNP.MinimumTCB.SNPVersion == nil {
return fmt.Errorf("field SNPVersion in manifest cannot be empty")
} else if r.SNP.MinimumTCB.MicrocodeVersion == nil {
return fmt.Errorf("field MicrocodeVersion in manifest cannot be empty")
}

if len(r.TrustedMeasurement) != 96 {
return fmt.Errorf("trusted measurement has invalid length: %d (expected 96)", len(r.TrustedMeasurement))
}

return nil
}

// Validate checks the validity of all fields in the manifest.
func (m *Manifest) Validate() error {
for policyHash := range m.Policies {
if _, err := policyHash.Bytes(); err != nil {
return fmt.Errorf("decoding policy hash %s: %w", policyHash, err)
} else if len(policyHash) != sha256.Size*2 {
return fmt.Errorf("policy hash %s has invalid length: %d (expected %d)", policyHash, len(policyHash), sha256.Size*2)
}
}

if err := m.ReferenceValues.Validate(); err != nil {
return fmt.Errorf("validating reference values: %w", err)
}

for _, keyDigest := range m.WorkloadOwnerKeyDigests {
if _, err := keyDigest.Bytes(); err != nil {
return fmt.Errorf("decoding key digest %s: %w", keyDigest, err)
} else if len(keyDigest) != sha256.Size*2 {
return fmt.Errorf("workload owner key digest %s has invalid length: %d (expected %d)", keyDigest, len(keyDigest), sha256.Size*2)
}
}

// TODO(davidweisse): validate SeedshareOwnerPubKeys field once it is being used.
return nil
}

// SNPValidateOpts returns validate options populated with the manifest's
// SNP reference values and trusted measurement.
func (m *Manifest) SNPValidateOpts() (*validate.Options, error) {
if err := m.Validate(); err != nil {
return nil, fmt.Errorf("validating manifest: %w", err)
}
trustedMeasurement, err := m.ReferenceValues.TrustedMeasurement.Bytes()
if err != nil {
return nil, fmt.Errorf("failed to convert TrustedMeasurement from manifest to byte slices: %w", err)
}
if trustedMeasurement == nil {
// This is required to prevent an empty measurement in the manifest from disabling the measurement check.
trustedMeasurement = make([]byte, 48)
}

if err = checkNullFields(m.ReferenceValues.SNP.MinimumTCB); err != nil {
return nil, err
}

return &validate.Options{
Measurement: trustedMeasurement,
Expand All @@ -164,16 +204,3 @@ func (m *Manifest) SNPValidateOpts() (*validate.Options, error) {
PermitProvisionalFirmware: true,
}, nil
}

func checkNullFields(tcb SNPTCB) error {
if tcb.BootloaderVersion == nil {
return fmt.Errorf("field BootloaderVersion in manifest cannot be empty")
} else if tcb.TEEVersion == nil {
return fmt.Errorf("field TEEVersion in manifest cannot be empty")
} else if tcb.SNPVersion == nil {
return fmt.Errorf("field SNPVersion in manifest cannot be empty")
} else if tcb.MicrocodeVersion == nil {
return fmt.Errorf("field MicrocodeVersion in manifest cannot be empty")
}
return nil
}
84 changes: 58 additions & 26 deletions internal/manifest/manifest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -147,59 +147,91 @@ func TestPolicy(t *testing.T) {
})
}

func TestSNPValidateOpts(t *testing.T) {
func TestValidate(t *testing.T) {
testCases := []struct {
tcb SNPTCB
tm HexString
m Manifest
wantErr bool
}{
{
tcb: SNPTCB{
BootloaderVersion: toPtr(SVN(0)),
TEEVersion: toPtr(SVN(1)),
SNPVersion: toPtr(SVN(2)),
MicrocodeVersion: toPtr(SVN(3)),
m: DefaultAKS(),
},
{
m: Default(),
wantErr: true,
},
{
m: Manifest{
Policies: map[HexString][]string{HexString(""): {}},
ReferenceValues: DefaultAKS().ReferenceValues,
},
wantErr: true,
},
{
m: Manifest{
Policies: map[HexString][]string{HexString(""): {}},
ReferenceValues: ReferenceValues{
SNP: Default().ReferenceValues.SNP,
TrustedMeasurement: "",
},
},
tm: HexString("000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"),
wantErr: true,
},
{
tcb: SNPTCB{},
m: Manifest{
ReferenceValues: Default().ReferenceValues,
WorkloadOwnerKeyDigests: []HexString{HexString("")},
},
wantErr: true,
},
}

for i, tc := range testCases {
t.Run(strconv.Itoa(i), func(t *testing.T) {
assert := assert.New(t)

mnfst := Manifest{
ReferenceValues: ReferenceValues{
SNP: SNPReferenceValues{MinimumTCB: tc.tcb},
TrustedMeasurement: tc.tm,
},
if tc.wantErr {
assert.Error(tc.m.Validate())
return
}
assert.NoError(tc.m.Validate())
})
}
}

func TestSNPValidateOpts(t *testing.T) {
testCases := []struct {
m Manifest
wantErr bool
}{
{m: DefaultAKS()},
{m: Default(), wantErr: true},
}

for i, tc := range testCases {
t.Run(strconv.Itoa(i), func(t *testing.T) {
assert := assert.New(t)

opts, err := mnfst.SNPValidateOpts()
opts, err := tc.m.SNPValidateOpts()
if tc.wantErr {
assert.Error(err)
return
}
assert.NoError(err)

assert.NotNil(tc.tcb.BootloaderVersion)
assert.NotNil(tc.tcb.TEEVersion)
assert.NotNil(tc.tcb.SNPVersion)
assert.NotNil(tc.tcb.MicrocodeVersion)
tcb := tc.m.ReferenceValues.SNP.MinimumTCB
assert.NotNil(tcb.BootloaderVersion)
assert.NotNil(tcb.TEEVersion)
assert.NotNil(tcb.SNPVersion)
assert.NotNil(tcb.MicrocodeVersion)

trustedMeasurement, err := tc.tm.Bytes()
trustedMeasurement, err := tc.m.ReferenceValues.TrustedMeasurement.Bytes()
assert.NoError(err)
assert.Equal(trustedMeasurement, opts.Measurement)

tcbParts := kds.TCBParts{
BlSpl: tc.tcb.BootloaderVersion.UInt8(),
TeeSpl: tc.tcb.TEEVersion.UInt8(),
SnpSpl: tc.tcb.SNPVersion.UInt8(),
UcodeSpl: tc.tcb.MicrocodeVersion.UInt8(),
BlSpl: tcb.BootloaderVersion.UInt8(),
TeeSpl: tcb.TEEVersion.UInt8(),
SnpSpl: tcb.SNPVersion.UInt8(),
UcodeSpl: tcb.MicrocodeVersion.UInt8(),
}
assert.Equal(tcbParts, opts.MinimumTCB)
assert.Equal(tcbParts, opts.MinimumLaunchTCB)
Expand Down

0 comments on commit caeaf76

Please sign in to comment.