diff --git a/cli/cmd/generate.go b/cli/cmd/generate.go index 889a51d3d5..24af601620 100644 --- a/cli/cmd/generate.go +++ b/cli/cmd/generate.go @@ -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 diff --git a/cli/cmd/set.go b/cli/cmd/set.go index 8267cde27d..a3ede2b1a6 100644 --- a/cli/cmd/set.go +++ b/cli/cmd/set.go @@ -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 { diff --git a/cli/cmd/verify.go b/cli/cmd/verify.go index 4bf93ca871..c3a0d7a5b1 100644 --- a/cli/cmd/verify.go +++ b/cli/cmd/verify.go @@ -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 { diff --git a/internal/manifest/manifest.go b/internal/manifest/manifest.go index a613255361..83ad4bdff9 100644 --- a/internal/manifest/manifest.go +++ b/internal/manifest/manifest.go @@ -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, @@ -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 -} diff --git a/internal/manifest/manifest_test.go b/internal/manifest/manifest_test.go index 6ba76e39d0..aa91e7ebb3 100644 --- a/internal/manifest/manifest_test.go +++ b/internal/manifest/manifest_test.go @@ -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)