mirror of
https://github.com/edgelesssys/constellation.git
synced 2025-01-23 22:01:14 -05:00
attestation: print ordered measurement verification warnings and errors (#2237)
Signed-off-by: Daniel Weiße <dw@edgeless.systems>
This commit is contained in:
parent
78fa921746
commit
103817a4a5
@ -226,6 +226,37 @@ func (m *M) EqualTo(other M) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Compare compares the expected measurements to the given list of measurements.
|
||||||
|
// It returns a list of warnings for non matching measurements for WarnOnly entries,
|
||||||
|
// and a list of errors for non matching measurements for Enforce entries.
|
||||||
|
func (m M) Compare(other map[uint32][]byte) (warnings []string, errs []error) {
|
||||||
|
// Get list of indices in expected measurements
|
||||||
|
var mIndices []uint32
|
||||||
|
for idx := range m {
|
||||||
|
mIndices = append(mIndices, idx)
|
||||||
|
}
|
||||||
|
sort.SliceStable(mIndices, func(i, j int) bool {
|
||||||
|
return mIndices[i] < mIndices[j]
|
||||||
|
})
|
||||||
|
|
||||||
|
for _, idx := range mIndices {
|
||||||
|
if !bytes.Equal(m[idx].Expected, other[idx]) {
|
||||||
|
msg := fmt.Sprintf("untrusted measurement value %x at index %d", other[idx], idx)
|
||||||
|
if len(other[idx]) == 0 {
|
||||||
|
msg = fmt.Sprintf("missing measurement value for index %d", idx)
|
||||||
|
}
|
||||||
|
|
||||||
|
if m[idx].ValidationOpt == Enforce {
|
||||||
|
errs = append(errs, errors.New(msg))
|
||||||
|
} else {
|
||||||
|
warnings = append(warnings, fmt.Sprintf("Encountered %s", msg))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings, errs
|
||||||
|
}
|
||||||
|
|
||||||
// GetEnforced returns a list of all enforced Measurements,
|
// GetEnforced returns a list of all enforced Measurements,
|
||||||
// i.e. all Measurements that are not marked as WarnOnly.
|
// i.e. all Measurements that are not marked as WarnOnly.
|
||||||
func (m *M) GetEnforced() []uint32 {
|
func (m *M) GetEnforced() []uint32 {
|
||||||
|
@ -7,6 +7,7 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package measurements
|
package measurements
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"io"
|
"io"
|
||||||
@ -928,3 +929,117 @@ func TestMergeImageMeasurementsV2(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestMeasurementsCompare(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
expected M
|
||||||
|
actual map[uint32][]byte
|
||||||
|
wantErrs int
|
||||||
|
wantWarnings int
|
||||||
|
}{
|
||||||
|
"no errors": {
|
||||||
|
expected: M{
|
||||||
|
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||||
|
1: WithAllBytes(0x11, Enforce, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
actual: map[uint32][]byte{
|
||||||
|
0: bytes.Repeat([]byte{0x00}, PCRMeasurementLength),
|
||||||
|
1: bytes.Repeat([]byte{0x11}, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
wantErrs: 0,
|
||||||
|
wantWarnings: 0,
|
||||||
|
},
|
||||||
|
"no errors, with warnings": {
|
||||||
|
expected: M{
|
||||||
|
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||||
|
1: WithAllBytes(0x11, WarnOnly, PCRMeasurementLength),
|
||||||
|
2: WithAllBytes(0x22, WarnOnly, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
actual: map[uint32][]byte{
|
||||||
|
0: bytes.Repeat([]byte{0x00}, PCRMeasurementLength),
|
||||||
|
1: bytes.Repeat([]byte{0xFF}, PCRMeasurementLength),
|
||||||
|
2: bytes.Repeat([]byte{0xFF}, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
wantErrs: 0,
|
||||||
|
wantWarnings: 2,
|
||||||
|
},
|
||||||
|
"with errors, no warnings": {
|
||||||
|
expected: M{
|
||||||
|
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||||
|
1: WithAllBytes(0x11, Enforce, PCRMeasurementLength),
|
||||||
|
2: WithAllBytes(0x22, Enforce, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
actual: map[uint32][]byte{
|
||||||
|
0: bytes.Repeat([]byte{0x00}, PCRMeasurementLength),
|
||||||
|
1: bytes.Repeat([]byte{0xFF}, PCRMeasurementLength),
|
||||||
|
2: bytes.Repeat([]byte{0xFF}, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
wantErrs: 2,
|
||||||
|
wantWarnings: 0,
|
||||||
|
},
|
||||||
|
"with errors and warnings": {
|
||||||
|
expected: M{
|
||||||
|
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||||
|
1: WithAllBytes(0x11, WarnOnly, PCRMeasurementLength),
|
||||||
|
2: WithAllBytes(0x22, Enforce, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
|
||||||
|
actual: map[uint32][]byte{
|
||||||
|
0: bytes.Repeat([]byte{0x00}, PCRMeasurementLength),
|
||||||
|
1: bytes.Repeat([]byte{0xFF}, PCRMeasurementLength),
|
||||||
|
2: bytes.Repeat([]byte{0xFF}, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
wantErrs: 1,
|
||||||
|
wantWarnings: 1,
|
||||||
|
},
|
||||||
|
"extra measurements don't cause errors": {
|
||||||
|
expected: M{
|
||||||
|
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||||
|
1: WithAllBytes(0x11, Enforce, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
actual: map[uint32][]byte{
|
||||||
|
0: bytes.Repeat([]byte{0x00}, PCRMeasurementLength),
|
||||||
|
1: bytes.Repeat([]byte{0x11}, PCRMeasurementLength),
|
||||||
|
2: bytes.Repeat([]byte{0x22}, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
wantErrs: 0,
|
||||||
|
wantWarnings: 0,
|
||||||
|
},
|
||||||
|
"missing measurements cause errors": {
|
||||||
|
expected: M{
|
||||||
|
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||||
|
1: WithAllBytes(0x11, Enforce, PCRMeasurementLength),
|
||||||
|
2: WithAllBytes(0x22, Enforce, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
actual: map[uint32][]byte{
|
||||||
|
0: bytes.Repeat([]byte{0x00}, PCRMeasurementLength),
|
||||||
|
1: bytes.Repeat([]byte{0x11}, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
wantErrs: 1,
|
||||||
|
wantWarnings: 0,
|
||||||
|
},
|
||||||
|
"missing measurements cause warnings": {
|
||||||
|
expected: M{
|
||||||
|
0: WithAllBytes(0x00, Enforce, PCRMeasurementLength),
|
||||||
|
1: WithAllBytes(0x11, Enforce, PCRMeasurementLength),
|
||||||
|
2: WithAllBytes(0x22, WarnOnly, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
actual: map[uint32][]byte{
|
||||||
|
0: bytes.Repeat([]byte{0x00}, PCRMeasurementLength),
|
||||||
|
1: bytes.Repeat([]byte{0x11}, PCRMeasurementLength),
|
||||||
|
},
|
||||||
|
wantErrs: 0,
|
||||||
|
wantWarnings: 1,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, tc := range testCases {
|
||||||
|
t.Run(name, func(t *testing.T) {
|
||||||
|
assert := assert.New(t)
|
||||||
|
|
||||||
|
gotWarnings, gotErrs := tc.expected.Compare(tc.actual)
|
||||||
|
assert.Equal(tc.wantErrs, len(gotErrs))
|
||||||
|
assert.Equal(tc.wantWarnings, len(gotWarnings))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -7,9 +7,9 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package tdx
|
package tdx
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
"github.com/edgelesssys/constellation/v2/internal/attestation"
|
||||||
@ -81,13 +81,12 @@ func (v *Validator) Validate(ctx context.Context, attDocRaw []byte, nonce []byte
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Verify the quote against the expected measurements.
|
// Verify the quote against the expected measurements.
|
||||||
for idx, ex := range v.expected {
|
warnings, errs := v.expected.Compare(tdMeasure)
|
||||||
if !bytes.Equal(ex.Expected, tdMeasure[idx]) {
|
for _, warning := range warnings {
|
||||||
if !ex.ValidationOpt {
|
v.log.Warnf(warning)
|
||||||
return nil, fmt.Errorf("untrusted TD measurement value at index %d", idx)
|
|
||||||
}
|
|
||||||
v.log.Warnf("Encountered untrusted TD measurement value at index %d", idx)
|
|
||||||
}
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, fmt.Errorf("measurement validation failed:\n%w", errors.Join(errs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
return attDoc.UserData, nil
|
return attDoc.UserData, nil
|
||||||
|
@ -7,10 +7,10 @@ SPDX-License-Identifier: AGPL-3.0-only
|
|||||||
package vtpm
|
package vtpm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"context"
|
"context"
|
||||||
"crypto"
|
"crypto"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
@ -219,21 +219,12 @@ func (v *Validator) Validate(ctx context.Context, attDocRaw []byte, nonce []byte
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
for idx, pcr := range v.expected {
|
warnings, errs := v.expected.Compare(attDoc.Attestation.Quotes[quoteIdx].Pcrs.Pcrs)
|
||||||
if !bytes.Equal(pcr.Expected[:], attDoc.Attestation.Quotes[quoteIdx].Pcrs.Pcrs[idx]) {
|
for _, warning := range warnings {
|
||||||
if pcr.ValidationOpt == measurements.Enforce {
|
v.log.Warnf(warning)
|
||||||
return nil, fmt.Errorf(
|
|
||||||
"untrusted PCR value %x at index %d",
|
|
||||||
attDoc.Attestation.Quotes[quoteIdx].Pcrs.Pcrs[idx],
|
|
||||||
idx,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
v.log.Warnf(
|
|
||||||
"Encountered untrusted PCR value %x at index %d",
|
|
||||||
attDoc.Attestation.Quotes[quoteIdx].Pcrs.Pcrs[idx],
|
|
||||||
idx,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
if len(errs) > 0 {
|
||||||
|
return nil, fmt.Errorf("measurement validation failed:\n%w", errors.Join(errs...))
|
||||||
}
|
}
|
||||||
|
|
||||||
v.log.Infof("Successfully validated attestation document")
|
v.log.Infof("Successfully validated attestation document")
|
||||||
|
@ -80,7 +80,7 @@ func TestValidate(t *testing.T) {
|
|||||||
defer tpmCloser.Close()
|
defer tpmCloser.Close()
|
||||||
|
|
||||||
issuer := NewIssuer(tpmOpen, tpmclient.AttestationKeyRSA, fakeGetInstanceInfo, logger.NewTest(t))
|
issuer := NewIssuer(tpmOpen, tpmclient.AttestationKeyRSA, fakeGetInstanceInfo, logger.NewTest(t))
|
||||||
validator := NewValidator(testExpectedPCRs, fakeGetTrustedKey, fakeValidateCVM, nil)
|
validator := NewValidator(testExpectedPCRs, fakeGetTrustedKey, fakeValidateCVM, logger.NewTest(t))
|
||||||
|
|
||||||
nonce := []byte{1, 2, 3, 4}
|
nonce := []byte{1, 2, 3, 4}
|
||||||
challenge := []byte("Constellation")
|
challenge := []byte("Constellation")
|
||||||
@ -206,6 +206,10 @@ func TestValidate(t *testing.T) {
|
|||||||
Expected: []byte{0xFF},
|
Expected: []byte{0xFF},
|
||||||
ValidationOpt: measurements.Enforce,
|
ValidationOpt: measurements.Enforce,
|
||||||
},
|
},
|
||||||
|
1: measurements.Measurement{
|
||||||
|
Expected: []byte{0xFF},
|
||||||
|
ValidationOpt: measurements.Enforce,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
fakeGetTrustedKey,
|
fakeGetTrustedKey,
|
||||||
fakeValidateCVM,
|
fakeValidateCVM,
|
||||||
@ -214,6 +218,25 @@ func TestValidate(t *testing.T) {
|
|||||||
nonce: nonce,
|
nonce: nonce,
|
||||||
wantErr: true,
|
wantErr: true,
|
||||||
},
|
},
|
||||||
|
"untrusted WarnOnly PCRs": {
|
||||||
|
validator: NewValidator(
|
||||||
|
measurements.M{
|
||||||
|
0: measurements.Measurement{
|
||||||
|
Expected: []byte{0xFF},
|
||||||
|
ValidationOpt: measurements.WarnOnly,
|
||||||
|
},
|
||||||
|
1: measurements.Measurement{
|
||||||
|
Expected: []byte{0xFF},
|
||||||
|
ValidationOpt: measurements.WarnOnly,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
fakeGetTrustedKey,
|
||||||
|
fakeValidateCVM,
|
||||||
|
logger.NewTest(t)),
|
||||||
|
attDoc: mustMarshalAttestation(attDoc, require),
|
||||||
|
nonce: nonce,
|
||||||
|
wantErr: false,
|
||||||
|
},
|
||||||
"no sha256 quote": {
|
"no sha256 quote": {
|
||||||
validator: NewValidator(testExpectedPCRs, fakeGetTrustedKey, fakeValidateCVM, warnLog),
|
validator: NewValidator(testExpectedPCRs, fakeGetTrustedKey, fakeValidateCVM, warnLog),
|
||||||
attDoc: mustMarshalAttestation(AttestationDocument{
|
attDoc: mustMarshalAttestation(AttestationDocument{
|
||||||
|
Loading…
Reference in New Issue
Block a user