From 09d89af62d59f8f00fd35c3d4586952a7cb0e00c Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 13 May 2026 14:49:18 +0100 Subject: [PATCH 01/18] fix: deterministic CBOR encoding - Enable encoding of nil containers as empty arrays rather than null (we usually omitempty so this mostly doesn't matter, but there some exceptions (e.g. conditional endorsement series condition) where we want to encode the empty value correctly. - Enable deterministic sorting of map keys for CoTS. Signed-off-by: Sergei Trofimov --- coev/cbor.go | 9 +++++---- comid/cbor.go | 7 ++++--- corim/cbor.go | 8 +++++--- cots/cbor.go | 8 +++++--- cots/eat_cwtclaims_test.go | 29 ++++++++++++++++------------- extensions/cbor.go | 7 ++++--- profiles/tdx/cbor.go | 7 ++++--- 7 files changed, 43 insertions(+), 32 deletions(-) diff --git a/coev/cbor.go b/coev/cbor.go index 8d2743a1..37e8197b 100644 --- a/coev/cbor.go +++ b/coev/cbor.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package coev @@ -38,9 +38,10 @@ func coevTags() cbor.TagSet { func initCBOREncMode() (en cbor.EncMode, err error) { encOpt := cbor.EncOptions{ - Sort: cbor.SortCoreDeterministic, - IndefLength: cbor.IndefLengthForbidden, - TimeTag: cbor.EncTagRequired, + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + NilContainers: cbor.NilContainerAsEmpty, + TimeTag: cbor.EncTagRequired, } return encOpt.EncModeWithTags(coevTags()) } diff --git a/comid/cbor.go b/comid/cbor.go index e8540b5d..7fdf4791 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -54,9 +54,10 @@ func comidTags() cbor.TagSet { func initCBOREncMode() (en cbor.EncMode, err error) { encOpt := cbor.EncOptions{ - Sort: cbor.SortCoreDeterministic, - IndefLength: cbor.IndefLengthForbidden, - TimeTag: cbor.EncTagRequired, + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + NilContainers: cbor.NilContainerAsEmpty, + TimeTag: cbor.EncTagRequired, } return encOpt.EncModeWithTags(comidTags()) } diff --git a/corim/cbor.go b/corim/cbor.go index 7b97aa6f..e522c694 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim @@ -45,8 +45,10 @@ func corimTags() cbor.TagSet { func initCBOREncMode() (en cbor.EncMode, err error) { encOpt := cbor.EncOptions{ - IndefLength: cbor.IndefLengthForbidden, - TimeTag: cbor.EncTagRequired, + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + NilContainers: cbor.NilContainerAsEmpty, + TimeTag: cbor.EncTagRequired, } return encOpt.EncModeWithTags(corimTags()) } diff --git a/cots/cbor.go b/cots/cbor.go index c426ce3f..e4b5005c 100644 --- a/cots/cbor.go +++ b/cots/cbor.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package cots @@ -42,8 +42,10 @@ func cotsTags() cbor.TagSet { func initCBOREncMode() (en cbor.EncMode, err error) { encOpt := cbor.EncOptions{ - IndefLength: cbor.IndefLengthForbidden, - TimeTag: cbor.EncTagRequired, + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + NilContainers: cbor.NilContainerAsEmpty, + TimeTag: cbor.EncTagRequired, } return encOpt.EncModeWithTags(cotsTags()) } diff --git a/cots/eat_cwtclaims_test.go b/cots/eat_cwtclaims_test.go index 151ee356..01131fc9 100644 --- a/cots/eat_cwtclaims_test.go +++ b/cots/eat_cwtclaims_test.go @@ -101,19 +101,22 @@ func TestEatCWTClaim_Full_RoundtripCBOR(t *testing.T) { tv := fatEatCWTClaim expected := []byte{ - 0xb0, 0xa, 0x48, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, - 0x51, 0x1, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, - 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xc, 0x69, - 0x41, 0x63, 0x6d, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0xd, - 0x46, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe, 0x3, 0xf, - 0xf5, 0x10, 0x1, 0x11, 0xa2, 0x1, 0xfb, 0x40, 0x28, 0xae, - 0x14, 0x7a, 0xe1, 0x47, 0xae, 0x2, 0xfb, 0x40, 0x4c, 0x63, - 0xd7, 0xa, 0x3d, 0x70, 0xa4, 0x13, 0x18, 0x3c, 0x1, 0x69, - 0x41, 0x63, 0x6d, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x2, - 0x67, 0x72, 0x72, 0x2d, 0x74, 0x72, 0x61, 0x70, 0x3, 0x69, - 0x41, 0x63, 0x6d, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, 0x4, - 0xc1, 0x0, 0x5, 0xc1, 0x0, 0x6, 0xc1, 0x0, 0x7, 0x46, 0xff, - 0xff, 0xff, 0xff, 0xff, 0xff, + 0xb0, 0x01, 0x69, 0x41, 0x63, 0x6d, 0x65, 0x20, + 0x49, 0x6e, 0x63, 0x2e, 0x02, 0x67, 0x72, 0x72, + 0x2d, 0x74, 0x72, 0x61, 0x70, 0x03, 0x69, 0x41, + 0x63, 0x6d, 0x65, 0x20, 0x49, 0x6e, 0x63, 0x2e, + 0x04, 0xc1, 0x00, 0x05, 0xc1, 0x00, 0x06, 0xc1, + 0x00, 0x07, 0x46, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0a, 0x48, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x0b, 0x51, 0x01, 0xde, 0xad, + 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, + 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0x0c, 0x69, + 0x41, 0x63, 0x6d, 0x65, 0x20, 0x49, 0x6e, 0x63, + 0x2e, 0x0d, 0x46, 0xff, 0xff, 0xff, 0xff, 0xff, + 0xff, 0x0e, 0x03, 0x0f, 0xf5, 0x10, 0x01, 0x11, + 0xa2, 0x01, 0xfb, 0x40, 0x28, 0xae, 0x14, 0x7a, + 0xe1, 0x47, 0xae, 0x02, 0xfb, 0x40, 0x4c, 0x63, + 0xd7, 0x0a, 0x3d, 0x70, 0xa4, 0x13, 0x18, 0x3c, } cborRoundTripper(t, &tv, expected) diff --git a/extensions/cbor.go b/extensions/cbor.go index 5c2dec81..f50e9d48 100644 --- a/extensions/cbor.go +++ b/extensions/cbor.go @@ -14,9 +14,10 @@ var ( func initCBOREncMode() (en cbor.EncMode, err error) { encOpt := cbor.EncOptions{ - Sort: cbor.SortCoreDeterministic, - IndefLength: cbor.IndefLengthForbidden, - TimeTag: cbor.EncTagRequired, + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + NilContainers: cbor.NilContainerAsEmpty, + TimeTag: cbor.EncTagRequired, } return encOpt.EncMode() } diff --git a/profiles/tdx/cbor.go b/profiles/tdx/cbor.go index e796f473..30a84e8d 100644 --- a/profiles/tdx/cbor.go +++ b/profiles/tdx/cbor.go @@ -40,9 +40,10 @@ func tdxTags() cbor.TagSet { func initCBOREncMode() (en cbor.EncMode, err error) { encOpt := cbor.EncOptions{ - Sort: cbor.SortCoreDeterministic, - IndefLength: cbor.IndefLengthForbidden, - TimeTag: cbor.EncTagRequired, + Sort: cbor.SortCoreDeterministic, + IndefLength: cbor.IndefLengthForbidden, + NilContainers: cbor.NilContainerAsEmpty, + TimeTag: cbor.EncTagRequired, } return encOpt.EncModeWithTags(tdxTags()) } From 4a2676990da0f9cd08c803b610222d8a6a78c331 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 13 May 2026 14:01:48 +0100 Subject: [PATCH 02/18] fix(comid): align CondEndorseSeriesTriple with rev. 10 Rev. 10 of the spec[1] defines the condition art of conditional-endorsement-series-triple-record as condition: [ environment: environment-map claims-list: [ * measurement-map ] ? authorized-by: [ + $crypto-key-type-choice ] ] Previous implementation used a type-aliased ValueTriple. This had two issues: - It did not allow specifying authorized-by - It did not allow an empty claims-list (reference and endorsement triples require at least one measurement and this is enforced by ValueTriple). Implement CondEndorseSeriesCondition with correct CBOR marshalling and use it instead of the ValueTriple. [1]: https://www.ietf.org/archive/id/draft-ietf-rats-corim-10.html#name-conditional-endorsement-ser Signed-off-by: Sergei Trofimov --- comid/cond_endorse_series_triple.go | 87 ++++++++++++- comid/cond_endorse_series_triple_test.go | 148 ++++++++++++++++++++--- comid/example_test.go | 4 +- comid/triples_test.go | 2 +- 4 files changed, 216 insertions(+), 25 deletions(-) diff --git a/comid/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go index b7a7b2b1..813f78b8 100644 --- a/comid/cond_endorse_series_triple.go +++ b/comid/cond_endorse_series_triple.go @@ -4,13 +4,83 @@ package comid import ( + "errors" "fmt" + "github.com/fxamacker/cbor/v2" "github.com/veraison/corim/extensions" ) -// A Stateful Environment is an Environment in a known reference state -type StatefulEnv = ValueTriple +// CondEndorseSeriesCondition represent the condtion part of a Conditional +// Endorsement Series Triple. +type CondEndorseSeriesCondition struct { + Environment Environment `json:"environment"` + Measurements Measurements `json:"measurements"` + AuthorizedBy *CryptoKeys `json:"authorized-by,omitempty"` +} + +func (o *CondEndorseSeriesCondition) RegisterExtensions(exts extensions.Map) error { + return o.Measurements.RegisterExtensions(exts) +} + +func (o *CondEndorseSeriesCondition) GetExtensions() extensions.IMapValue { + return o.Measurements.GetExtensions() +} + +func (o *CondEndorseSeriesCondition) Valid() error { + if err := o.Environment.Valid(); err != nil { + return fmt.Errorf("environment validation failed: %w", err) + } + + if err := o.Measurements.Valid(); err != nil { + return fmt.Errorf("measurements validation failed: %w", err) + } + + if o.AuthorizedBy != nil { + if err := o.AuthorizedBy.Valid(); err != nil { + return fmt.Errorf("authorized-by validation failed: %w", err) + } + } + + return nil +} + +func (o *CondEndorseSeriesCondition) MarshalCBOR() ([]byte, error) { + toMarshal := []any{o.Environment, o.Measurements} + if o.AuthorizedBy != nil { + toMarshal = append(toMarshal, o.AuthorizedBy) + } + + return em.Marshal(toMarshal) +} + +func (o *CondEndorseSeriesCondition) UnmarshalCBOR(data []byte) error { + var raw []cbor.RawMessage + if err := dm.Unmarshal(data, &raw); err != nil { + return err + } + + numElts := len(raw) + if numElts < 2 || numElts > 3 { + return fmt.Errorf("expected array between 2 and 3 elements; found %d", numElts) + } + + if err := dm.Unmarshal(raw[0], &o.Environment); err != nil { + return fmt.Errorf("environment: %w", err) + } + + if err := dm.Unmarshal(raw[1], &o.Measurements); err != nil { + return fmt.Errorf("measurements: %w", err) + } + + if numElts == 3 { + if err := dm.Unmarshal(raw[2], &o.AuthorizedBy); err != nil { + return fmt.Errorf("authorized-by: %w", err) + } + } + + return nil +} // A Conditional Endorsement Series Record, has a series of conditions identified by // the selection which are matched with the Attester Actual State(from Evidence) @@ -60,9 +130,9 @@ func NewCondEndorseSeriesRecords() *CondEndorseSeriesRecords { // on an initial condition match (specified by Condition StatefulEnv) followed by a series // condition match (specified in selection: inside conditional-series-record). type CondEndorseSeriesTriple struct { - _ struct{} `cbor:",toarray"` - Condition StatefulEnv `json:"statefulenv"` - Series CondEndorseSeriesRecords `json:"series"` + _ struct{} `cbor:",toarray"` + Condition CondEndorseSeriesCondition `json:"condition"` + Series CondEndorseSeriesRecords `json:"series"` } // RegisterExtensions accepts MVal and MFlag Extension points, that will be registered with @@ -81,8 +151,13 @@ func (o *CondEndorseSeriesTriple) RegisterExtensions(exts extensions.Map) error // nolint:gocritic func (o CondEndorseSeriesTriple) Valid() error { if err := o.Condition.Valid(); err != nil { - return fmt.Errorf("stateful environment validation failed: %w", err) + return fmt.Errorf("condition validation failed: %w", err) + } + + if o.Series.IsEmpty() { + return errors.New("empty conditional series") } + if err := o.Series.Valid(); err != nil { return fmt.Errorf("conditional series validation failed: %w", err) } diff --git a/comid/cond_endorse_series_triple_test.go b/comid/cond_endorse_series_triple_test.go index 89cc5e5b..91551350 100644 --- a/comid/cond_endorse_series_triple_test.go +++ b/comid/cond_endorse_series_triple_test.go @@ -22,7 +22,7 @@ func Test_CondEndorseSeriesTriple_Add_Valid(t *testing.T) { c := NewCondEndorseSeriesTriples() // Create a valid triple triple := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -35,7 +35,7 @@ func Test_CondEndorseSeriesTriple_Add_Valid(t *testing.T) { c.Add(triple) assert.False(t, c.IsEmpty()) err := c.Valid() - assert.Contains(t, err.Error(), "no measurement entries") + assert.ErrorContains(t, err, "empty conditional series") } func Test_CondEndorseSeriesTriple_Valid_EmptyCondition(t *testing.T) { @@ -45,13 +45,13 @@ func Test_CondEndorseSeriesTriple_Valid_EmptyCondition(t *testing.T) { } ces.Add(ces_triple) err := ces.Valid() - assert.EqualError(t, err, "error at index 0: stateful environment validation failed: environment validation failed: environment must not be empty") + assert.EqualError(t, err, "error at index 0: condition validation failed: environment validation failed: environment must not be empty") } func Test_CondEndorseSeriesTriple_Valid_InvalidCondition(t *testing.T) { c := NewCondEndorseSeriesTriples() series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -75,7 +75,7 @@ func Test_CondEndorseSeriesTriple_Valid_InvalidSeries(t *testing.T) { c := NewCondEndorseSeriesTriples() // Create series with invalid record series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -95,13 +95,13 @@ func Test_CondEndorseSeriesTriple_Valid_InvalidSeries(t *testing.T) { series.Series.Add(invalidRecord) c.Add(series) err := c.Valid() - assert.ErrorContains(t, err, "no measurement entries") + assert.ErrorContains(t, err, "no measurement value") } func Test_CondEndorseSeriesTriple_Valid_EmptySeries(t *testing.T) { c := NewCondEndorseSeriesTriples() series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -112,7 +112,7 @@ func Test_CondEndorseSeriesTriple_Valid_EmptySeries(t *testing.T) { } c.Add(series) err := c.Valid() - assert.ErrorContains(t, err, "no measurement entries") + assert.ErrorContains(t, err, "empty conditional series") } func Test_CondEndorseSeriesTriple_Valid_ValidSeries(t *testing.T) { @@ -139,7 +139,7 @@ func Test_CondEndorseSeriesTriple_Valid_ValidSeries(t *testing.T) { } series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -170,7 +170,7 @@ func Test_CondEndorseSeriesTriple_RegisterExtensions_OK(t *testing.T) { extMap := extensions.NewMap(). Add(ExtMval, &testExtensions{}) series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -188,7 +188,7 @@ func Test_CondEndorseSeriesTriple_RegisterExtensions_NOK(t *testing.T) { extMap := extensions.NewMap(). Add(ExtReferenceValue, &testExtensions{}) series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -207,7 +207,7 @@ func Test_CondEndorseSeriesTriple_RegisterExtensions_SeriesError(t *testing.T) { // Create series with record that will cause extension registration error series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -363,7 +363,7 @@ func Test_CondEndorseSeriesRecords_RegisterExtensions_Error(t *testing.T) { func Test_CondEndorseSeriesTriple_GetExtensions(t *testing.T) { series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -381,7 +381,7 @@ func Test_CondEndorseSeriesTriple_MarshalJSON(t *testing.T) { // Create valid triple triple := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -416,7 +416,7 @@ func Test_CondEndorseSeriesTriple_MarshalCBOR(t *testing.T) { // Create valid triple triple := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -437,7 +437,7 @@ func Test_CondEndorseSeriesTriple_UnmarshalCBOR(t *testing.T) { // First marshal a valid triple to CBOR triple := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -649,3 +649,119 @@ func Test_CondEndorseSeriesRecords_GetExtensions(t *testing.T) { exts := records.GetExtensions() assert.Nil(t, exts) } + +func Test_CondEndorseSeriesCondition_serialize_round_trip(t *testing.T) { + testCases := []struct { + title string + condition CondEndorseSeriesCondition + expectedCBOR []byte + expectedJSON string + }{ + { + title: "minimal", + condition: CondEndorseSeriesCondition{ + Environment: Environment{ + Class: &Class{ + ClassID: MustNewUUIDClassID(TestUUID), + }, + }, + }, + expectedCBOR: []byte{ + 0x82, // array(2) [condition] + 0xa1, // . [0]map(1) [environment] + 0x0, // . . key: 0 [class] + 0xa1, // . . value: map(1) [class] + 0x0, // . . . key: 0 [class-id] + 0xd8, 0x25, // . . . value: tag(37) [uuid] + 0x50, // . . . . bstr(16) + 0x31, 0xfb, 0x5a, 0xbf, 0x02, 0x3e, 0x49, 0x92, + 0xaa, 0x4e, 0x95, 0xf9, 0xc1, 0x50, 0x3b, 0xfa, + 0x80, // . [1]array(0) [measurements] + }, + expectedJSON: `{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}},"measurements":null}`, + }, + { + title: "all fields", + condition: CondEndorseSeriesCondition{ + Environment: Environment{ + Class: &Class{ + ClassID: MustNewUUIDClassID(TestUUID), + }, + }, + Measurements: *NewMeasurements().Add((&Measurement{}).SetSVN(5)), + AuthorizedBy: NewCryptoKeys().Add(MustNewPKIXBase64Key(TestECPubKey)), + }, + expectedCBOR: []byte{ + 0x83, // array(3) [condition] + 0xa1, // . [0]map(1) [environment] + 0x0, // . . key: 0 [class] + 0xa1, // . . value: map(1) [class] + 0x0, // . . . key: 0 [class-id] + 0xd8, 0x25, // . . . value: tag(37) [uuid] + 0x50, // . . . . bstr(16) + 0x31, 0xfb, 0x5a, 0xbf, 0x02, 0x3e, 0x49, 0x92, + 0xaa, 0x4e, 0x95, 0xf9, 0xc1, 0x50, 0x3b, 0xfa, + 0x81, // . [1]array(0) [measurements] + 0xa1, // . . [0]map(1) [measurement] + 0x01, // . . . key: 1 [val] + 0xa1, // . . . value: map(1) [mval] + 0x01, // . . . . key: 1 [svn] + 0xd9, 0x02, 0x28, // . . . . value: tag(552) [svn] + 0x05, // . . . . . 5 + 0x81, // . [2]array(1) [authorized-by] + 0xd9, 0x02, 0x2a, // . . [0]tag(554) [pkix-base64-key] + 0x78, 0xb1, // . . . tstr(177) + 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x42, 0x45, 0x47, + 0x49, 0x4e, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, + 0x43, 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, + 0x2d, 0x2d, 0x0a, 0x4d, 0x46, 0x6b, 0x77, 0x45, // 32 + + 0x77, 0x59, 0x48, 0x4b, 0x6f, 0x5a, 0x49, 0x7a, + 0x6a, 0x30, 0x43, 0x41, 0x51, 0x59, 0x49, 0x4b, + 0x6f, 0x5a, 0x49, 0x7a, 0x6a, 0x30, 0x44, 0x41, + 0x51, 0x63, 0x44, 0x51, 0x67, 0x41, 0x45, 0x57, // 64 + + 0x31, 0x42, 0x76, 0x71, 0x46, 0x2b, 0x2f, 0x72, + 0x79, 0x38, 0x42, 0x57, 0x61, 0x37, 0x5a, 0x45, + 0x4d, 0x55, 0x31, 0x78, 0x59, 0x59, 0x48, 0x45, + 0x51, 0x38, 0x42, 0x0a, 0x6c, 0x4c, 0x54, 0x34, // 96 + + 0x4d, 0x46, 0x48, 0x4f, 0x61, 0x4f, 0x2b, 0x49, + 0x43, 0x54, 0x74, 0x49, 0x76, 0x72, 0x45, 0x65, + 0x45, 0x70, 0x72, 0x2f, 0x73, 0x66, 0x54, 0x41, + 0x50, 0x36, 0x36, 0x48, 0x32, 0x68, 0x43, 0x48, // 128 + + 0x64, 0x62, 0x35, 0x48, 0x45, 0x58, 0x4b, 0x74, + 0x52, 0x4b, 0x6f, 0x64, 0x36, 0x51, 0x4c, 0x63, + 0x4f, 0x4c, 0x50, 0x41, 0x31, 0x51, 0x3d, 0x3d, + 0x0a, 0x2d, 0x2d, 0x2d, 0x2d, 0x2d, 0x45, 0x4e, // 160 + + 0x44, 0x20, 0x50, 0x55, 0x42, 0x4c, 0x49, 0x43, + 0x20, 0x4b, 0x45, 0x59, 0x2d, 0x2d, 0x2d, 0x2d, + 0x2d, // 177 + }, + expectedJSON: `{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}},"measurements":[{"value":{"svn":{"type":"exact-value","value":5}}}],"authorized-by":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}`, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + bytes, err := em.Marshal(&tc.condition) + assert.NoError(t, err) + assert.Equal(t, tc.expectedCBOR, bytes) + + var decoded CondEndorseSeriesCondition + err = dm.Unmarshal(bytes, &decoded) + assert.NoError(t, err) + assert.EqualValues(t, tc.condition.Environment, decoded.Environment) + + bytes, err = json.Marshal(&tc.condition) + assert.NoError(t, err) + assert.Equal(t, tc.expectedJSON, string(bytes)) + + err = json.Unmarshal(bytes, &decoded) + assert.NoError(t, err) + assert.EqualValues(t, tc.condition.Environment, decoded.Environment) + }) + } +} diff --git a/comid/example_test.go b/comid/example_test.go index 61830b7a..a842a472 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -99,7 +99,7 @@ func Example_encode() { ). AddCondEndorseSeries( &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: NewClassOID(TestOID). SetVendor("ACME Ltd."). @@ -162,7 +162,7 @@ func Example_encode() { // Output: // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a5008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d08818282a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa818281a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a501d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a3064802005e1000000001075020010db8000000000000000000000068094702deadbeefdead - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"conditional-endorsement-series":[{"statefulenv":{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]},"series":[{"selection":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w=="}}],"addition":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","ueid":"At6tvu/erQ=="}}]}]}]}} + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"conditional-endorsement-series":[{"condition":{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]},"series":[{"selection":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w=="}}],"addition":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","ueid":"At6tvu/erQ=="}}]}]}]}} } func Example_encode_dependency_triples() { diff --git a/comid/triples_test.go b/comid/triples_test.go index 8d5b1544..24d68b69 100644 --- a/comid/triples_test.go +++ b/comid/triples_test.go @@ -83,7 +83,7 @@ func TestTriples_Valid(t *testing.T) { triples.CondEndorseSeries = &CondEndorseSeriesTriples{} triples.CondEndorseSeries.Add(&CondEndorseSeriesTriple{}) err = triples.Valid() - assert.EqualError(t, err, "conditional series: error at index 0: stateful environment validation failed: environment validation failed: environment must not be empty") + assert.EqualError(t, err, "conditional series: error at index 0: condition validation failed: environment validation failed: environment must not be empty") } func TestTriples_adders(t *testing.T) { From fa028fd91c5e462f9e5fa5c28520e793eddc158f Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Wed, 13 May 2026 15:18:02 +0100 Subject: [PATCH 03/18] feat(comid): add conditional endorsement triples Implement conditional endorsement triples as described in section 5.1.7 of rev. 10 of the spec[1]. [1]: https://www.ietf.org/archive/id/draft-ietf-rats-corim-10.html#name-conditional-endorsement-tri Signed-off-by: Sergei Trofimov --- comid/cond_endorse_triple.go | 84 ++++++++++++++++++ comid/cond_endorse_triple_test.go | 140 ++++++++++++++++++++++++++++++ comid/extensions.go | 2 + comid/triples.go | 48 +++++++++- corim/profiles.go | 4 + 5 files changed, 276 insertions(+), 2 deletions(-) create mode 100644 comid/cond_endorse_triple.go create mode 100644 comid/cond_endorse_triple_test.go diff --git a/comid/cond_endorse_triple.go b/comid/cond_endorse_triple.go new file mode 100644 index 00000000..e0867905 --- /dev/null +++ b/comid/cond_endorse_triple.go @@ -0,0 +1,84 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "errors" + "fmt" + + "github.com/veraison/corim/extensions" +) + +// StatefulEnvironment describes the state of the target environment that is +// being matched by a CondEndorseTriple. +type StatefulEnvironment = ValueTriple + +// StatefulEnvironments is a container for StatefulEnvironment instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type StatefulEnvironments = ValueTriples + +func NewStatefulEnvironments() *StatefulEnvironments { + return NewValueTriples() +} + +// CondEndorseTriple declares one or more conditions that, once matched, +// results in augmenting the Attester's actual state with the Endorsement +// Claims. It corresponds to CDDL conditional-endorsement-triple-record: +// +// conditional-endorsement-triple-record = [ +// conditions: [ + stateful-environment-record ] +// endorsements: [ + endorsed-triple-record ] +// ] +// +// stateful-environment-record = [ +// environment: environment-map, +// claims-list: [ + measurement-map ] +// ] +// +// (draft-ietf-rats-corim §5.1.7) +type CondEndorseTriple struct { + _ struct{} `cbor:",toarray"` + Conditions StatefulEnvironments `json:"conditions"` + Endorsements ValueTriples `json:"endorsements"` +} + +func (o *CondEndorseTriple) RegisterExtensions(exts extensions.Map) error { + if err := o.Conditions.RegisterExtensions(exts); err != nil { + return err + } + + return o.Endorsements.RegisterExtensions(exts) +} + +func (o *CondEndorseTriple) GetExtensions() extensions.IMapValue { + return o.Endorsements.GetExtensions() +} + +func (o CondEndorseTriple) Valid() error { + if o.Conditions.IsEmpty() { + return errors.New("conditions validation failed: no condition entries") + } + + if err := o.Conditions.Valid(); err != nil { + return fmt.Errorf("conditions validation failed: %w", err) + } + + if o.Endorsements.IsEmpty() { + return errors.New("endorsements validation failed: no endorsement entries") + } + + if err := o.Endorsements.Valid(); err != nil { + return fmt.Errorf("endorsements validation failed: %w", err) + } + + return nil +} + +// CondEndorseTriples is a container for CondEndorseTriple instances and their extensions. +// It is a thin wrapper around extensions.Collection. +type CondEndorseTriples = extensions.Collection[CondEndorseTriple, *CondEndorseTriple] + +func NewCondEndorseTriples() *CondEndorseTriples { + return extensions.NewCollection[CondEndorseTriple]() +} diff --git a/comid/cond_endorse_triple_test.go b/comid/cond_endorse_triple_test.go new file mode 100644 index 00000000..e64c96a3 --- /dev/null +++ b/comid/cond_endorse_triple_test.go @@ -0,0 +1,140 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/veraison/corim/extensions" +) + +func TestCondEndorseTriple_Valid(t *testing.T) { + testCases := []struct { + title string + triple CondEndorseTriple + err string + }{ + { + title: "bad: empty conditions", + triple: CondEndorseTriple{}, + err: "no condition entries", + }, + { + title: "bad: invalid stateful environment", + triple: CondEndorseTriple{ + Conditions: *NewStatefulEnvironments().Add(&StatefulEnvironment{}), + }, + err: "environment must not be empty", + }, + { + title: "bad: empty endorsements", + triple: CondEndorseTriple{ + Conditions: *NewStatefulEnvironments().Add( + &StatefulEnvironment{ + Environment: Environment{ + Instance: MustNewUUIDInstance(TestUUID), + }, + Measurements: *NewMeasurements().Add( + &Measurement{ + Val: Mval{ + SVN: MustNewSVN(1, "exact-value"), + }, + }, + ), + }, + ), + }, + err: "no endorsement entries", + }, + { + title: "bad: invalid endorsements", + triple: CondEndorseTriple{ + Conditions: *NewStatefulEnvironments().Add( + &StatefulEnvironment{ + Environment: Environment{ + Instance: MustNewUUIDInstance(TestUUID), + }, + Measurements: *NewMeasurements().Add( + &Measurement{ + Val: Mval{ + SVN: MustNewSVN(1, "exact-value"), + }, + }, + ), + }, + ), + Endorsements: *NewValueTriples().Add(&ValueTriple{}), + }, + err: "environment must not be empty", + }, + { + title: "ok", + triple: CondEndorseTriple{ + Conditions: *NewStatefulEnvironments().Add( + &StatefulEnvironment{ + Environment: Environment{ + Instance: MustNewUUIDInstance(TestUUID), + }, + Measurements: *NewMeasurements().Add( + &Measurement{ + Val: Mval{ + SVN: MustNewSVN(1, "exact-value"), + }, + }, + ), + }, + ), + Endorsements: *NewValueTriples().Add( + &StatefulEnvironment{ + Environment: Environment{ + Instance: MustNewUUIDInstance(TestUUID), + }, + Measurements: *NewMeasurements().Add( + &Measurement{ + Val: Mval{ + SVN: MustNewSVN(1, "exact-value"), + }, + }, + ), + }, + ), + }, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + err := tc.triple.Valid() + if tc.err == "" { + assert.NoError(t, err) + } else { + assert.ErrorContains(t, err, tc.err) + } + }) + } +} + +func TestCondEndorseTriple_extensions(t *testing.T) { + exts := extensions.NewMap() + exts.Add(ExtMval, &struct{}{}) + + triple := &CondEndorseTriple{} + err := triple.RegisterExtensions(exts) + assert.NoError(t, err) + + ret := triple.GetExtensions() + assert.NotNil(t, ret) + assert.EqualValues(t, ret, exts) + + exts.Add(ExtEntity, &struct{}{}) + err = triple.RegisterExtensions(exts) + assert.ErrorContains(t, err, "unexpected extension point") +} + +func TestNewCondEndorseTriples(t *testing.T) { + triples := NewCondEndorseTriples() + assert.NotNil(t, triples) + assert.True(t, triples.IsEmpty()) +} diff --git a/comid/extensions.go b/comid/extensions.go index 584d3337..2109cf71 100644 --- a/comid/extensions.go +++ b/comid/extensions.go @@ -14,6 +14,8 @@ const ( ExtReferenceValueFlags extensions.Point = "ReferenceValueFlags" ExtEndorsedValue extensions.Point = "EndorsedValue" ExtEndorsedValueFlags extensions.Point = "EndorsedValueFlags" + ExtCondEndorseValue extensions.Point = "CondEndorseValue" + ExtCondEndorseValueFlags extensions.Point = "CondEndorseValueFlags" ExtCondEndorseSeriesValue extensions.Point = "CondEndorseSeriesValue" ExtCondEndorseSeriesValueFlags extensions.Point = "CondEndorseSeriesValueFlags" ExtMval extensions.Point = "Mval" diff --git a/comid/triples.go b/comid/triples.go index 5f52babf..9fb8fc6a 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -18,6 +18,7 @@ type Triples struct { AttestVerifKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"attester-verification-keys,omitempty"` DomainDependencies *DomainDependencyTriples `cbor:"4,keyasint,omitempty" json:"dependency-triples,omitempty"` CondEndorseSeries *CondEndorseSeriesTriples `cbor:"8,keyasint,omitempty" json:"conditional-endorsement-series,omitempty"` + CondEndorsements *CondEndorseTriples `cbor:"10,keyasint,omitempty" json:"conditional-endorsements,omitempty"` Extensions } @@ -26,6 +27,7 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { refValExts := extensions.NewMap() endValExts := extensions.NewMap() conSeriesExts := extensions.NewMap() + conExts := extensions.NewMap() for p, v := range exts { switch p { @@ -39,6 +41,10 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { endValExts[ExtMval] = v case ExtEndorsedValueFlags: endValExts[ExtFlags] = v + case ExtCondEndorseValue: + conExts[ExtMval] = v + case ExtCondEndorseValueFlags: + conExts[ExtFlags] = v case ExtCondEndorseSeriesValue: conSeriesExts[ExtMval] = v case ExtCondEndorseSeriesValueFlags: @@ -78,6 +84,16 @@ func (o *Triples) RegisterExtensions(exts extensions.Map) error { } } + if len(conExts) != 0 { + if o.CondEndorsements == nil { + o.CondEndorsements = NewCondEndorseTriples() + } + + if err := o.CondEndorsements.RegisterExtensions(conExts); err != nil { + return err + } + } + return nil } @@ -91,6 +107,7 @@ func (o *Triples) UnmarshalCBOR(data []byte) error { return encoding.PopulateStructFromCBOR(dm, data, o) } +// nolint:gocritic // MarshalCBOR serializes to CBOR func (o Triples) MarshalCBOR() ([]byte, error) { // If extensions have been registered, the collection will exist, but @@ -111,6 +128,10 @@ func (o Triples) MarshalCBOR() ([]byte, error) { o.CondEndorseSeries = nil } + if o.CondEndorsements != nil && o.CondEndorsements.IsEmpty() { + o.CondEndorsements = nil + } + if o.DomainDependencies != nil && o.DomainDependencies.IsEmpty() { o.DomainDependencies = nil } @@ -123,6 +144,7 @@ func (o *Triples) UnmarshalJSON(data []byte) error { return encoding.PopulateStructFromJSON(data, o) } +// nolint:gocritic // MarshalJSON serializes to JSON func (o Triples) MarshalJSON() ([]byte, error) { // If extensions have been registered, the collection will exist, but @@ -143,6 +165,10 @@ func (o Triples) MarshalJSON() ([]byte, error) { o.CondEndorseSeries = nil } + if o.CondEndorsements != nil && o.CondEndorsements.IsEmpty() { + o.CondEndorsements = nil + } + if o.DomainDependencies != nil && o.DomainDependencies.IsEmpty() { o.DomainDependencies = nil } @@ -215,7 +241,7 @@ func (o *Triples) IterDevIdentityKeys() iter.Seq[*KeyTriple] { } // Valid checks that the Triples is valid as per the specification -func (o Triples) Valid() error { +func (o *Triples) Valid() error { // non-empty<> if (o.ReferenceValues == nil || o.ReferenceValues.IsEmpty()) && (o.EndorsedValues == nil || o.EndorsedValues.IsEmpty()) && @@ -266,7 +292,13 @@ func (o Triples) Valid() error { } } - return o.validTriples(&o) + if o.CondEndorsements != nil { + if err := o.CondEndorsements.Valid(); err != nil { + return fmt.Errorf("conditional endorsements: %w", err) + } + } + + return o.validTriples(o) } func (o *Triples) AddReferenceValue(val *ValueTriple) *Triples { @@ -333,3 +365,15 @@ func (o *Triples) AddCondEndorseSeries(val *CondEndorseSeriesTriple) *Triples { return o } + +func (o *Triples) AddCondEndorsement(val *CondEndorseTriple) *Triples { + if o != nil { + if o.CondEndorsements == nil { + o.CondEndorsements = NewCondEndorseTriples() + } + + o.CondEndorsements.Add(val) + } + + return o +} diff --git a/corim/profiles.go b/corim/profiles.go index 145c11e6..0bca926a 100644 --- a/corim/profiles.go +++ b/corim/profiles.go @@ -39,6 +39,10 @@ var ComidMapExtensionPoints = []extensions.Point{ comid.ExtReferenceValueFlags, comid.ExtEndorsedValue, comid.ExtEndorsedValueFlags, + comid.ExtCondEndorseValue, + comid.ExtCondEndorseValueFlags, + comid.ExtCondEndorseSeriesValue, + comid.ExtCondEndorseSeriesValueFlags, } // AllExtensionPoints is a list of all valid extension.Point's From 43bc68b0ef9f02c9c92951f68ebdb79d8c4732f6 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 14 May 2026 08:48:42 +0100 Subject: [PATCH 04/18] fix(comid): update Locator to rev. 10 Update Locator to allow multiple href's and thumbprints. In both cases, if only one element is present in the field, it serializes as a single item, otherwise the field is serialized as an array. Signed-off-by: Sergei Trofimov --- corim/oneormore.go | 87 +++++++++++++++++++++ corim/oneormore_test.go | 149 ++++++++++++++++++++++++++++++++++++ corim/unsignedcorim.go | 27 +++++-- corim/unsignedcorim_test.go | 8 +- 4 files changed, 259 insertions(+), 12 deletions(-) create mode 100644 corim/oneormore.go create mode 100644 corim/oneormore_test.go diff --git a/corim/oneormore.go b/corim/oneormore.go new file mode 100644 index 00000000..60dd3991 --- /dev/null +++ b/corim/oneormore.go @@ -0,0 +1,87 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "encoding/json" + "errors" +) + +// OneOrMore is a slice that serializes as either a single item when it +// contains only one element, or an array of items if it contains more than one +// element. It is invalid for OneOrMore to contain no elements. +type OneOrMore[T any] []T + +// Add the specified values to the OneOrMore. +func (o *OneOrMore[T]) Add(v ...T) *OneOrMore[T] { + *o = append(*o, v...) + return o +} + +// Valid returns an error if the OneOrMore or more is invalid (i.e. contains no +// elements). +func (o OneOrMore[T]) Valid() error { + if len(o) == 0 { + return errors.New("must have at least one") + } + + return nil +} + +func (o OneOrMore[T]) MarshalCBOR() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + if len(o) == 1 { + return em.Marshal(o[0]) + } else { + return em.Marshal([]T(o)) + } +} + +func (o *OneOrMore[T]) UnmarshalCBOR(data []byte) error { + var vals []T + if err := dm.Unmarshal(data, &vals); err == nil { + *o = vals + return nil + } + + var one T + if err := dm.Unmarshal(data, &one); err != nil { + return err + } + + *o = []T{one} + + return nil +} + +func (o OneOrMore[T]) MarshalJSON() ([]byte, error) { + if err := o.Valid(); err != nil { + return nil, err + } + + if len(o) == 1 { + return json.Marshal(o[0]) + } else { + return json.Marshal([]T(o)) + } +} + +func (o *OneOrMore[T]) UnmarshalJSON(data []byte) error { + var vals []T + if err := json.Unmarshal(data, &vals); err == nil { + *o = vals + return nil + } + + var one T + if err := json.Unmarshal(data, &one); err != nil { + return err + } + + *o = []T{one} + + return nil +} diff --git a/corim/oneormore_test.go b/corim/oneormore_test.go new file mode 100644 index 00000000..10b922fd --- /dev/null +++ b/corim/oneormore_test.go @@ -0,0 +1,149 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/veraison/corim/comid" + "github.com/veraison/swid" +) + +func Test_OneOrMore_serilize_round_trip(t *testing.T) { + stringCases := []struct { + title string + oom OneOrMore[string] + expectedCBOR []byte + expectedJSON string + }{ + { + title: "one string", + oom: OneOrMore[string]{"foo"}, + expectedCBOR: []byte{ + 0x63, // tstr(3) + 0x66, 0x6f, 0x6f, // . "foo" + }, + expectedJSON: `"foo"`, + }, + { + title: "more strings", + oom: OneOrMore[string]{"foo", "bar"}, + expectedCBOR: []byte{ + 0x82, // array(2) + 0x63, // . [0]tstr(3) + 0x66, 0x6f, 0x6f, // . . "foo" + 0x63, // . [1]tstr(3) + 0x62, 0x61, 0x72, // . . "bar" + }, + expectedJSON: `["foo","bar"]`, + }, + } + + for _, tc := range stringCases { + t.Run(tc.title, func(t *testing.T) { + encoded, err := tc.oom.MarshalCBOR() + assert.NoError(t, err) + assert.Equal(t, tc.expectedCBOR, encoded) + + var decoded OneOrMore[string] + err = decoded.UnmarshalCBOR(encoded) + assert.NoError(t, err) + assert.Equal(t, tc.oom, decoded) + + encoded, err = tc.oom.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, tc.expectedJSON, string(encoded)) + + decoded = OneOrMore[string]{} + err = decoded.UnmarshalJSON(encoded) + assert.NoError(t, err) + assert.Equal(t, tc.oom, decoded) + }) + } + + hash1 := *comid.NewHashEntry( + swid.Sha256, + comid.MustHexDecode(t, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + ) + hash2 := *comid.NewHashEntry( + swid.Sha256, + comid.MustHexDecode(t, "c0decafec0decafec0decafec0decafec0decafec0decafec0decafec0decafe"), + ) + + digestCases := []struct { + title string + oom OneOrMore[swid.HashEntry] + expectdCBOR []byte + expectedJSON string + }{ + { + title: "one digest", + oom: OneOrMore[swid.HashEntry]{hash1}, + expectdCBOR: []byte{ + 0x82, // array(2) + 0x01, // . [0]1 [sha-256] + 0x58, 0x20, // . [1]bstr(32) + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, // 32 + }, + expectedJSON: `"sha-256;3q2+796tvu/erb7v3q2+796tvu/erb7v3q2+796tvu8="`, + }, + { + title: "more digests", + oom: OneOrMore[swid.HashEntry]{hash1, hash2}, + expectdCBOR: []byte{ + 0x82, // array(2) + 0x82, // . [0]array(2) + 0x01, // . . [0]1 [sha-256] + 0x58, 0x20, // . . [1]bstr(32) + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, + 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, // 32 + 0x82, // . [1]array(2) + 0x01, // . . [0]1 [sha-256] + 0x58, 0x20, // . . [1]bstr(32) + 0xc0, 0xde, 0xca, 0xfe, 0xc0, 0xde, 0xca, 0xfe, + 0xc0, 0xde, 0xca, 0xfe, 0xc0, 0xde, 0xca, 0xfe, + 0xc0, 0xde, 0xca, 0xfe, 0xc0, 0xde, 0xca, 0xfe, + 0xc0, 0xde, 0xca, 0xfe, 0xc0, 0xde, 0xca, 0xfe, // 32 + }, + expectedJSON: `["sha-256;3q2+796tvu/erb7v3q2+796tvu/erb7v3q2+796tvu8=","sha-256;wN7K/sDeyv7A3sr+wN7K/sDeyv7A3sr+wN7K/sDeyv4="]`, + }, + } + + for _, tc := range digestCases { + t.Run(tc.title, func(t *testing.T) { + encoded, err := tc.oom.MarshalCBOR() + assert.NoError(t, err) + assert.Equal(t, tc.expectdCBOR, encoded) + + var decoded OneOrMore[swid.HashEntry] + err = decoded.UnmarshalCBOR(encoded) + assert.NoError(t, err) + assert.Equal(t, tc.oom, decoded) + + encoded, err = tc.oom.MarshalJSON() + assert.NoError(t, err) + assert.Equal(t, tc.expectedJSON, string(encoded)) + + decoded = OneOrMore[swid.HashEntry]{} + err = decoded.UnmarshalJSON(encoded) + assert.NoError(t, err) + assert.Equal(t, tc.oom, decoded) + }) + } +} + +func Test_OneOrMore_Valid(t *testing.T) { + oom := OneOrMore[int]{} + err := oom.Valid() + assert.ErrorContains(t, err, "must have at least one") + + oom.Add(1) + err = oom.Valid() + assert.NoError(t, err) +} diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 6cd32f65..f6334bb2 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -157,8 +157,11 @@ func (o *UnsignedCorim) AddCoswid(c *swid.SoftwareIdentity) *UnsignedCorim { func (o *UnsignedCorim) AddDependentRim(href string, thumbprint *swid.HashEntry) *UnsignedCorim { if o != nil { l := Locator{ - Href: comid.TaggedURI(href), - Thumbprint: thumbprint, + Href: OneOrMore[comid.TaggedURI]{comid.TaggedURI(href)}, + } + + if thumbprint != nil { + l.Thumbprint = &OneOrMore[swid.HashEntry]{*thumbprint} } if o.DependentRims == nil { @@ -570,18 +573,26 @@ func (o *Tag) UnmarshalJSON(data []byte) error { // Locator is the internal representation of the corim-locator-map with CBOR and // JSON serialization. type Locator struct { - Href comid.TaggedURI `cbor:"0,keyasint" json:"href"` - Thumbprint *swid.HashEntry `cbor:"1,keyasint,omitempty" json:"thumbprint,omitempty"` + Href OneOrMore[comid.TaggedURI] `cbor:"0,keyasint" json:"href"` + Thumbprint *OneOrMore[swid.HashEntry] `cbor:"1,keyasint,omitempty" json:"thumbprint,omitempty"` } func (o Locator) Valid() error { - if o.Href.Empty() { - return errors.New("empty href") + if err := o.Href.Valid(); err != nil { + return fmt.Errorf("href: %w", err) + } + + if o.Thumbprint == nil { + return nil + } + + if err := o.Thumbprint.Valid(); err != nil { + return fmt.Errorf("thumbprint: %w", err) } - if tp := o.Thumbprint; tp != nil { + for i, tp := range *o.Thumbprint { if err := swid.ValidHashEntry(tp.HashAlgID, tp.HashValue); err != nil { - return fmt.Errorf("invalid locator thumbprint: %w", err) + return fmt.Errorf("invalid locator thumbprint at index %d: %w", i, err) } } diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 88ea39d9..15e9d25b 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -429,13 +429,13 @@ func TestUnsignedCorim_truncated(t *testing.T) { func TestLocator_Valid(t *testing.T) { l := Locator{} - assert.EqualError(t, l.Valid(), "empty href") + assert.EqualError(t, l.Valid(), "href: must have at least one") - l.Href = comid.TaggedURI("https://example.com") + l.Href = OneOrMore[comid.TaggedURI]{comid.TaggedURI("https://example.com")} assert.NoError(t, l.Valid()) - l.Thumbprint = &swid.HashEntry{} - assert.EqualError(t, l.Valid(), "invalid locator thumbprint: unknown hash algorithm 0") + l.Thumbprint = &OneOrMore[swid.HashEntry]{swid.HashEntry{}} + assert.EqualError(t, l.Valid(), "invalid locator thumbprint at index 0: unknown hash algorithm 0") } From f60cdd92ffaf55feb3b2ebb75d9edbbc46d5cbf9 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 14 May 2026 11:54:31 +0100 Subject: [PATCH 05/18] feat(comid): add domain membership triple Implement domain membership triples as described in section 5.1.1.11.1 of rev. 10 of the spec[1]. [1]: https://www.ietf.org/archive/id/draft-ietf-rats-corim-10.html#name-domain-membership-triple Signed-off-by: Sergei Trofimov --- comid/domain_membership_triple.go | 89 ++++++++++++++++++++++++++ comid/domain_membership_triple_test.go | 55 ++++++++++++++++ comid/triples.go | 32 ++++++++- 3 files changed, 175 insertions(+), 1 deletion(-) create mode 100644 comid/domain_membership_triple.go create mode 100644 comid/domain_membership_triple_test.go diff --git a/comid/domain_membership_triple.go b/comid/domain_membership_triple.go new file mode 100644 index 00000000..db2bc3e1 --- /dev/null +++ b/comid/domain_membership_triple.go @@ -0,0 +1,89 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package comid + +import ( + "errors" + "fmt" +) + +// DomainMembershipTriple is the CDDL domain-membership-triple-record: +// +// domain-membership-triple-record = [ +// domain-id: domain-type, +// members: [ + domain-type ] +// ] +// domain-type = environment-map +// +// A DMT links a domain to its member domains. It allows an endorser to issue +// an authoritative statement about the composition of an Attester as a +// collection of environments. +// (draft-ietf-rats-corim §5.1.11.1). +type DomainMembershipTriple struct { + _ struct{} `cbor:",toarray"` + DomainID Environment `json:"domain-id"` + Members []Environment `json:"members"` +} + +// AddMember adds provided Environment as a member to the +// DomainMembershipTriple. +func (o *DomainMembershipTriple) AddMember(env Environment) *DomainMembershipTriple { + o.Members = append(o.Members, env) + return o +} + +// Valid returns an error if the DomainMembershipTriple does not have any +// members or contains invalid environments. +func (o DomainMembershipTriple) Valid() error { + if err := o.DomainID.Valid(); err != nil { + return fmt.Errorf("domain-id: %w", err) + } + + if len(o.Members) == 0 { + return errors.New("must have at least one member") + } + + for i, m := range o.Members { + if err := m.Valid(); err != nil { + return fmt.Errorf("member[%d]: %w", i, err) + } + } + + return nil +} + +// DomainMembershipTriples is a container of DomainMembershipTriple instances. +type DomainMembershipTriples []DomainMembershipTriple + +// NewDomainMebershipTriples returns a new empty NewDomainMebershipTriples. +func NewDomainMebershipTriples() *DomainMembershipTriples { + return &DomainMembershipTriples{} +} + +// Add a triple to the DomainMembershipTriples. +func (o *DomainMembershipTriples) Add(triple DomainMembershipTriple) *DomainMembershipTriples { + *o = append(*o, triple) + return o +} + +// Valid retursn an error if DomainMembershipTriples is empty or conatains +// invalid elements. +func (o DomainMembershipTriples) Valid() error { + if len(o) == 0 { + return errors.New("must not be empty") + } + + for i, triple := range o { + if err := triple.Valid(); err != nil { + return fmt.Errorf("triple[%d]: %w", i, err) + } + } + + return nil +} + +// IsEmpty returns true if the DomainMembershipTriples does not contain any +// triples. +func (o DomainMembershipTriples) IsEmpty() bool { + return len(o) == 0 +} diff --git a/comid/domain_membership_triple_test.go b/comid/domain_membership_triple_test.go new file mode 100644 index 00000000..398e9009 --- /dev/null +++ b/comid/domain_membership_triple_test.go @@ -0,0 +1,55 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func Test_DomainMembershipTriple_Valid(t *testing.T) { + triple := DomainMembershipTriple{} + err := triple.Valid() + assert.ErrorContains(t, err, "environment must not be empty") + + triple.DomainID = Environment{ + Instance: MustNewUUIDInstance(TestUUID), + } + + err = triple.Valid() + assert.ErrorContains(t, err, "must have at least one member") + + triple.AddMember(Environment{}) + + err = triple.Valid() + assert.ErrorContains(t, err, "member[0]: environment must not be empty") + + triple.Members[0].Instance = MustNewUUIDInstance(TestUUID) + + err = triple.Valid() + assert.NoError(t, err) +} + +func Test_DomainMembershipTriples_Valid(t *testing.T) { + triples := NewDomainMebershipTriples() + assert.True(t, triples.IsEmpty()) + err := triples.Valid() + assert.ErrorContains(t, err, "must not be empty") + + triples.Add(DomainMembershipTriple{ + DomainID: Environment{ + Instance: MustNewUUIDInstance(TestUUID), + }, + }) + + err = triples.Valid() + assert.ErrorContains(t, err, "triple[0]: must have at least one member") + + (*triples)[0].AddMember(Environment{ + Instance: MustNewUUIDInstance(TestUUID), + }) + + err = triples.Valid() + assert.NoError(t, err) +} diff --git a/comid/triples.go b/comid/triples.go index 9fb8fc6a..89197fd0 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -17,6 +17,7 @@ type Triples struct { DevIdentityKeys *KeyTriples `cbor:"2,keyasint,omitempty" json:"dev-identity-keys,omitempty"` AttestVerifKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"attester-verification-keys,omitempty"` DomainDependencies *DomainDependencyTriples `cbor:"4,keyasint,omitempty" json:"dependency-triples,omitempty"` + DomainMemberships *DomainMembershipTriples `cbor:"5,keyasint,omitempty" json:"membership-triples,omitempty"` CondEndorseSeries *CondEndorseSeriesTriples `cbor:"8,keyasint,omitempty" json:"conditional-endorsement-series,omitempty"` CondEndorsements *CondEndorseTriples `cbor:"10,keyasint,omitempty" json:"conditional-endorsements,omitempty"` Extensions @@ -136,6 +137,10 @@ func (o Triples) MarshalCBOR() ([]byte, error) { o.DomainDependencies = nil } + if o.DomainMemberships != nil && o.DomainMemberships.IsEmpty() { + o.DomainMemberships = nil + } + return encoding.SerializeStructToCBOR(em, o) } @@ -173,6 +178,10 @@ func (o Triples) MarshalJSON() ([]byte, error) { o.DomainDependencies = nil } + if o.DomainMemberships != nil && o.DomainMemberships.IsEmpty() { + o.DomainMemberships = nil + } + return encoding.SerializeStructToJSON(o) } @@ -241,6 +250,7 @@ func (o *Triples) IterDevIdentityKeys() iter.Seq[*KeyTriple] { } // Valid checks that the Triples is valid as per the specification +// nolint:gocyclo func (o *Triples) Valid() error { // non-empty<> if (o.ReferenceValues == nil || o.ReferenceValues.IsEmpty()) && @@ -248,7 +258,9 @@ func (o *Triples) Valid() error { (o.AttestVerifKeys == nil || len(*o.AttestVerifKeys) == 0) && (o.DevIdentityKeys == nil || len(*o.DevIdentityKeys) == 0) && (o.DomainDependencies == nil || o.DomainDependencies.IsEmpty()) && - (o.CondEndorseSeries == nil || o.CondEndorseSeries.IsEmpty()) { + (o.DomainMemberships == nil || o.DomainMemberships.IsEmpty()) && + (o.CondEndorseSeries == nil || o.CondEndorseSeries.IsEmpty()) && + (o.CondEndorsements == nil || o.CondEndorsements.IsEmpty()) { return fmt.Errorf("triples struct must not be empty") } @@ -286,6 +298,12 @@ func (o *Triples) Valid() error { } } + if o.DomainMemberships != nil { + if err := o.DomainMemberships.Valid(); err != nil { + return fmt.Errorf("membership triples: %w", err) + } + } + if o.CondEndorseSeries != nil { if err := o.CondEndorseSeries.Valid(); err != nil { return fmt.Errorf("conditional series: %w", err) @@ -353,6 +371,18 @@ func (o *Triples) AddDomainDependency(val *DomainDependencyTriple) *Triples { return o } +// AddDomainMembership appends a domain-dependency-triple to DomainDependencies. +func (o *Triples) AddDomainMembership(val *DomainMembershipTriple) *Triples { + if o != nil { + if o.DomainMemberships == nil { + o.DomainMemberships = new(DomainMembershipTriples) + } + *o.DomainMemberships = append(*o.DomainMemberships, *val) + } + + return o +} + // nolint:gocritic func (o *Triples) AddCondEndorseSeries(val *CondEndorseSeriesTriple) *Triples { if o != nil { From c42ee37700775cbeb3617c7f6b052e6d9a3a08de Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 14 May 2026 12:05:09 +0100 Subject: [PATCH 06/18] fix(comid)!: remove support for integer class ID Integer has been removed as a valid class ID variant since rev. 5. BREAKING CHANGE: integer is no longer a valid type for class ID. Signed-off-by: Sergei Trofimov --- comid/cbor.go | 1 - comid/classid.go | 97 ------------------------------------------- comid/classid_test.go | 63 +--------------------------- go.mod | 1 - go.sum | 2 - 5 files changed, 1 insertion(+), 163 deletions(-) diff --git a/comid/cbor.go b/comid/cbor.go index 7fdf4791..3a7ba4bd 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -20,7 +20,6 @@ var ( 111: TaggedOID{}, // CoMID tags 550: TaggedUEID{}, - 551: TaggedInt(0), 552: TaggedSVN(0), 553: TaggedMinSVN(0), 554: TaggedPKIXBase64Key(""), diff --git a/comid/classid.go b/comid/classid.go index 34b080f3..ee582341 100644 --- a/comid/classid.go +++ b/comid/classid.go @@ -4,13 +4,10 @@ package comid import ( - "encoding/binary" "encoding/json" "errors" "fmt" - "strconv" - "fortio.org/safecast" "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" ) @@ -230,99 +227,6 @@ func MustNewUUIDClassID(val any) *ClassID { return ret } -const IntType = "int" - -type TaggedInt int - -func NewIntClassID(val any) (*ClassID, error) { - if val == nil { - zeroVal := TaggedInt(0) - return &ClassID{&zeroVal}, nil - } - - var ret TaggedInt - - switch t := val.(type) { - case string: - i, err := strconv.Atoi(t) - if err != nil { - return nil, fmt.Errorf("bad int: %w", err) - } - ret = TaggedInt(i) - case []byte: - if len(t) != 8 { - return nil, fmt.Errorf("bad int: want 8 bytes, got %d bytes", len(t)) - } - ti, err := safecast.Convert[int, uint64](binary.BigEndian.Uint64(t)) - if err != nil { - return nil, err - } - ret = TaggedInt(ti) - case int: - ret = TaggedInt(t) - case *int: - ret = TaggedInt(*t) - case int64: - ti, err := safecast.Convert[int, int64](t) - if err != nil { - return nil, err - } - ret = TaggedInt(ti) - case *int64: - ti, err := safecast.Convert[int, int64](*t) - if err != nil { - return nil, err - } - ret = TaggedInt(ti) - case uint64: - ti, err := safecast.Convert[int, uint64](t) - if err != nil { - return nil, err - } - ret = TaggedInt(ti) - case *uint64: - ti, err := safecast.Convert[int, uint64](*t) - if err != nil { - return nil, err - } - ret = TaggedInt(ti) - default: - return nil, fmt.Errorf("unexpected type for int: %T", t) - } - - if err := ret.Valid(); err != nil { - return nil, err - } - - return &ClassID{&ret}, nil -} - -func (o TaggedInt) String() string { - return fmt.Sprint(int(o)) -} - -func (o TaggedInt) Valid() error { - return nil -} - -func (o TaggedInt) Type() string { - return "int" -} - -func (o TaggedInt) Bytes() []byte { - var ret [8]byte - var uo uint64 - io := int(o) // Needed for gosec flow typing. - // Use 2's complement for negative values since this can't return an error. - if io < 0 { - uo = ^uint64(0) - uint64(-io) + 1 - } else { - uo = uint64(io) - } - binary.BigEndian.PutUint64(ret[:], uo) - return ret[:] -} - // MustNewBytesClassID is like NewBytesClassID except it does not return an // error, assuming that the provided value is valid. It panics if that isn't // the case. @@ -359,7 +263,6 @@ type IClassIDFactory func(any) (*ClassID, error) var classIDValueRegister = map[string]IClassIDFactory{ OIDType: NewOIDClassID, UUIDType: NewUUIDClassID, - IntType: NewIntClassID, BytesType: NewBytesClassID, } diff --git a/comid/classid_test.go b/comid/classid_test.go index 0bae2315..5f529896 100644 --- a/comid/classid_test.go +++ b/comid/classid_test.go @@ -1,10 +1,9 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import ( - "encoding/binary" "encoding/json" "fmt" "testing" @@ -296,66 +295,6 @@ func Test_NewOIDClassID(t *testing.T) { assert.Equal(t, taggedOID.Bytes(), classID.Bytes()) } -func Test_NewIntClassID(t *testing.T) { - classID, err := NewIntClassID(nil) - require.NoError(t, err) - assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}, classID.Bytes()) - - testInt := 7 - testInt64 := int64(7) - testUint64 := uint64(7) - - var testBytes [8]byte - binary.BigEndian.PutUint64(testBytes[:], testUint64) - - for _, v := range []any{ - testInt, - &testInt, - testInt64, - &testInt64, - testUint64, - &testUint64, - "7", - testBytes[:], - } { - classID, err = NewIntClassID(v) - require.NoError(t, err) - got := classID.Bytes() - assert.Equal(t, testBytes[:], got) - } -} - -func Test_TaggedInt(t *testing.T) { - val := TaggedInt(7) - assert.Equal(t, "7", val.String()) - assert.Equal(t, []byte{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x07}, val.Bytes()) - assert.Equal(t, "int", val.Type()) - assert.NoError(t, val.Valid()) - - classID := ClassID{&val} - - bytes, err := em.Marshal(classID) - require.NoError(t, err) - assert.Equal(t, []byte{ - 0xd9, 0x02, 0x27, // tag 551 - 0x07, // int 7 - }, bytes) - - var out ClassID - err = dm.Unmarshal(bytes, &out) - require.NoError(t, err) - assert.Equal(t, classID, out) - - jsonBytes, err := json.Marshal(classID) - require.NoError(t, err) - assert.Equal(t, `{"type":"int","value":7}`, string(jsonBytes)) - - out = ClassID{} - err = json.Unmarshal(jsonBytes, &out) - require.NoError(t, err) - assert.Equal(t, classID, out) -} - type testClassID [4]byte func newTestClassID(_ any) (*ClassID, error) { diff --git a/go.mod b/go.mod index 1a4c4771..ba275296 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,6 @@ module github.com/veraison/corim go 1.23.0 require ( - fortio.org/safecast v1.0.0 github.com/fxamacker/cbor/v2 v2.8.0 github.com/google/uuid v1.3.0 github.com/lestrrat-go/jwx/v2 v2.0.21 diff --git a/go.sum b/go.sum index dbaf0e75..5d10ad46 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,3 @@ -fortio.org/safecast v1.0.0 h1:dr3131WPX8iS1pTf76+39WeXbTrerDYLvi9s7Oi3wiY= -fortio.org/safecast v1.0.0/go.mod h1:xZmcPk3vi4kuUFf+tq4SvnlVdwViqf6ZSZl91Jr9Jdg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= From 65a8dc4019712bd3ee0f1a0dc0135c0a2df34332 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 14 May 2026 13:19:29 +0100 Subject: [PATCH 07/18] feat(comid): add support for x509 DER CryptoKeys Add support for ASN1 DER x509 certificate as a CryptoKey variant. This was introduced in rev. 6 of the spec. Signed-off-by: Sergei Trofimov --- comid/cbor.go | 1 + comid/cryptokey.go | 73 ++++++++++++++++++++++++++++++++++++++++- comid/cryptokey_test.go | 25 +++++++++++++- comid/test_vars.go | 64 ++++++++++++++++++++++++++++++++++++ 4 files changed, 161 insertions(+), 2 deletions(-) diff --git a/comid/cbor.go b/comid/cbor.go index 3a7ba4bd..58abdc28 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -30,6 +30,7 @@ var ( 559: TaggedCertThumbprint{}, 560: TaggedBytes{}, 561: TaggedCertPathThumbprint{}, + 562: TaggedPKIXAsn1DerCert{}, 564: TaggedRawIntRange{}, } ) diff --git a/comid/cryptokey.go b/comid/cryptokey.go index 0bcfda55..2c1f4eb7 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -1,4 +1,4 @@ -// Copyright 2023 Contributors to the Veraison project. +// Copyright 2023-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -35,6 +35,8 @@ const ( // COSEKeyType represents a CBOR encoded COSE_Key or COSE_KeySet. See // https://www.rfc-editor.org/rfc/rfc9052#section-7 COSEKeyType = "cose-key" + // PKIXBase64CertType indicates an X.509 public key certificate. + PKIXAsn1DerCertType = "pkix-asn1der-cert" // ThumbprintType represents a digest of a raw public key. The digest // value may be used to find the public key if contained in a lookup // table. @@ -566,6 +568,74 @@ func (o TaggedCOSEKey) coseKeySet() ([]*cose.Key, error) { return keySet, nil } +// TaggedPKIXAsn1DerCert is a X.509 public key certificate. +type TaggedPKIXAsn1DerCert []byte + +func NewPKIXAsn1DerCert(k any) (*CryptoKey, error) { + b, ok := k.([]byte) + if !ok { + return nil, fmt.Errorf("value must be a []byte; found %T", k) + } + + cert := TaggedPKIXAsn1DerCert(b) + if err := cert.Valid(); err != nil { + return nil, err + } + return &CryptoKey{cert}, nil +} + +func MustNewPKIXAsn1DerCert(k any) *CryptoKey { + cert, err := NewPKIXAsn1DerCert(k) + if err != nil { + panic(err) + } + return cert +} + +func (o TaggedPKIXAsn1DerCert) String() string { + block := &pem.Block{ + Type: "CERTIFICATE", + Bytes: o, + } + + return string(pem.EncodeToMemory(block)) +} + +func (o TaggedPKIXAsn1DerCert) Valid() error { + _, err := o.cert() + return err +} + +func (o TaggedPKIXAsn1DerCert) Type() string { + return PKIXBase64CertType +} + +func (o TaggedPKIXAsn1DerCert) PublicKey() (crypto.PublicKey, error) { + cert, err := o.cert() + if err != nil { + return nil, err + } + + if cert.PublicKey == nil { + return nil, errors.New("cert does not contain a crypto.PublicKey") + } + + return cert.PublicKey, nil +} + +func (o TaggedPKIXAsn1DerCert) cert() (*x509.Certificate, error) { + if len(o) == 0 { + return nil, errors.New("cert value not set") + } + + cert, err := x509.ParseCertificate(o) + if err != nil { + return nil, fmt.Errorf("could not parse x509 cert: %w", err) + } + + return cert, nil +} + type digest struct { swid.HashEntry } @@ -735,6 +805,7 @@ var cryptoKeyValueRegister = map[string]ICryptoKeyFactory{ PKIXBase64CertType: NewPKIXBase64Cert, PKIXBase64CertPathType: NewPKIXBase64CertPath, COSEKeyType: NewCOSEKey, + PKIXAsn1DerCertType: NewPKIXAsn1DerCert, ThumbprintType: NewThumbprint, CertThumbprintType: NewCertThumbprint, CertPathThumbprintType: NewCertPathThumbprint, diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index 38bd6e25..af9cf1ca 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -1,4 +1,4 @@ -// Copyright 2023-2024 Contributors to the Veraison project. +// Copyright 2023-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -8,6 +8,7 @@ import ( "encoding/base64" "encoding/json" "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -482,3 +483,25 @@ func Test_RegisterCryptoKey(t *testing.T) { require.NoError(t, err) assert.Equal(t, key, &out2) } + +func Test_CryptoKey_NewPKIXAsn1DerCert(t *testing.T) { + cert, err := NewPKIXAsn1DerCert(TestCertDER) + require.NoError(t, err) + assert.Equal(t, TestCert, strings.TrimSpace(cert.String())) + pub, err := cert.PublicKey() + assert.NoError(t, err) + assert.NotNil(t, pub) + + _, err = NewPKIXAsn1DerCert("foo") + assert.ErrorContains(t, err, "value must be a []byte") + + _, err = NewPKIXAsn1DerCert([]byte{}) + assert.EqualError(t, err, "cert value not set") + + _, err = NewPKIXAsn1DerCert([]byte{0xde, 0xad, 0xbe, 0xef}) + assert.ErrorContains(t, err, "could not parse x509 cert") + + assert.Panics(t, func() { + MustNewPKIXAsn1DerCert("") + }) +} diff --git a/comid/test_vars.go b/comid/test_vars.go index 572a3a1c..af6bb8de 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -61,6 +61,70 @@ f9h8GxeIDUnLqldeIvNfa+9SAiEA9ULBTPjnTUhYle226OAjg2sdhkXtb3Mu0E0F nuUmsIQ= -----END CERTIFICATE-----` + TestCertDER = []byte{ + 0x30, 0x82, 0x01, 0xe1, 0x30, 0x82, 0x01, 0x87, + 0xa0, 0x03, 0x02, 0x01, 0x02, 0x02, 0x14, 0x1a, + 0x1a, 0xc0, 0xf4, 0xcd, 0xf2, 0x40, 0x81, 0x6a, + 0x72, 0x40, 0x36, 0xbf, 0xa9, 0xd0, 0x7b, 0x09, + 0x85, 0xdf, 0x42, 0x30, 0x0a, 0x06, 0x08, 0x2a, + 0x86, 0x48, 0xce, 0x3d, 0x04, 0x03, 0x02, 0x30, + 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, 0x55, + 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, 0x13, + 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, 0x0c, + 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, 0x74, + 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, 0x06, + 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, 0x6e, + 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, 0x57, + 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, 0x50, + 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30, 0x20, + 0x17, 0x0d, 0x32, 0x33, 0x30, 0x39, 0x30, 0x34, + 0x31, 0x31, 0x30, 0x31, 0x34, 0x38, 0x5a, 0x18, + 0x0f, 0x32, 0x30, 0x35, 0x31, 0x30, 0x31, 0x31, + 0x39, 0x31, 0x31, 0x30, 0x31, 0x34, 0x38, 0x5a, + 0x30, 0x45, 0x31, 0x0b, 0x30, 0x09, 0x06, 0x03, + 0x55, 0x04, 0x06, 0x13, 0x02, 0x41, 0x55, 0x31, + 0x13, 0x30, 0x11, 0x06, 0x03, 0x55, 0x04, 0x08, + 0x0c, 0x0a, 0x53, 0x6f, 0x6d, 0x65, 0x2d, 0x53, + 0x74, 0x61, 0x74, 0x65, 0x31, 0x21, 0x30, 0x1f, + 0x06, 0x03, 0x55, 0x04, 0x0a, 0x0c, 0x18, 0x49, + 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x65, 0x74, 0x20, + 0x57, 0x69, 0x64, 0x67, 0x69, 0x74, 0x73, 0x20, + 0x50, 0x74, 0x79, 0x20, 0x4c, 0x74, 0x64, 0x30, + 0x59, 0x30, 0x13, 0x06, 0x07, 0x2a, 0x86, 0x48, + 0xce, 0x3d, 0x02, 0x01, 0x06, 0x08, 0x2a, 0x86, + 0x48, 0xce, 0x3d, 0x03, 0x01, 0x07, 0x03, 0x42, + 0x00, 0x04, 0x5b, 0x50, 0x6f, 0xa8, 0x5f, 0xbf, + 0xaf, 0x2f, 0x01, 0x59, 0xae, 0xd9, 0x10, 0xc5, + 0x35, 0xc5, 0x86, 0x07, 0x11, 0x0f, 0x01, 0x94, + 0xb4, 0xf8, 0x30, 0x51, 0xce, 0x68, 0xef, 0x88, + 0x09, 0x3b, 0x48, 0xbe, 0xb1, 0x1e, 0x12, 0x9a, + 0xff, 0xb1, 0xf4, 0xc0, 0x3f, 0xae, 0x87, 0xda, + 0x10, 0x87, 0x75, 0xbe, 0x47, 0x11, 0x72, 0xad, + 0x44, 0xaa, 0x1d, 0xe9, 0x02, 0xdc, 0x38, 0xb3, + 0xc0, 0xd5, 0xa3, 0x53, 0x30, 0x51, 0x30, 0x1d, + 0x06, 0x03, 0x55, 0x1d, 0x0e, 0x04, 0x16, 0x04, + 0x14, 0x16, 0xa4, 0xd3, 0xdb, 0xe9, 0xe5, 0x83, + 0x48, 0xcf, 0xfe, 0x8f, 0x0a, 0x5b, 0xce, 0x83, + 0xb7, 0x88, 0x78, 0x38, 0x2d, 0x30, 0x1f, 0x06, + 0x03, 0x55, 0x1d, 0x23, 0x04, 0x18, 0x30, 0x16, + 0x80, 0x14, 0x16, 0xa4, 0xd3, 0xdb, 0xe9, 0xe5, + 0x83, 0x48, 0xcf, 0xfe, 0x8f, 0x0a, 0x5b, 0xce, + 0x83, 0xb7, 0x88, 0x78, 0x38, 0x2d, 0x30, 0x0f, + 0x06, 0x03, 0x55, 0x1d, 0x13, 0x01, 0x01, 0xff, + 0x04, 0x05, 0x30, 0x03, 0x01, 0x01, 0xff, 0x30, + 0x0a, 0x06, 0x08, 0x2a, 0x86, 0x48, 0xce, 0x3d, + 0x04, 0x03, 0x02, 0x03, 0x48, 0x00, 0x30, 0x45, + 0x02, 0x20, 0x06, 0xb2, 0x34, 0x81, 0x74, 0x78, + 0x22, 0x43, 0x66, 0x67, 0x2b, 0xa9, 0x18, 0xc7, + 0x7f, 0xd8, 0x7c, 0x1b, 0x17, 0x88, 0x0d, 0x49, + 0xcb, 0xaa, 0x57, 0x5e, 0x22, 0xf3, 0x5f, 0x6b, + 0xef, 0x52, 0x02, 0x21, 0x00, 0xf5, 0x42, 0xc1, + 0x4c, 0xf8, 0xe7, 0x4d, 0x48, 0x58, 0x95, 0xed, + 0xb6, 0xe8, 0xe0, 0x23, 0x83, 0x6b, 0x1d, 0x86, + 0x45, 0xed, 0x6f, 0x73, 0x2e, 0xd0, 0x4d, 0x05, + 0x9e, 0xe5, 0x26, 0xb0, 0x84, + } + TestCertPath = `-----BEGIN CERTIFICATE----- MIICejCCAiygAwIBAgIUIpeVwVhN/qYLgtNJlwZHJj+IT/wwBQYDK2VwMDMxMTAv BgNVBAUTKDdhMDZlZWU0MWI3ODlmNDg2M2Q4NmI4Nzc4YjFhMjAxYTZmZWRkNTYw From 6f9cdaacda2d139953f4ce4f8b1dbe84b25de6e1 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Thu, 14 May 2026 18:30:09 +0100 Subject: [PATCH 08/18] feat(comid)!: masked raw values + rev10 alignment Re-implement raw value to add the masked variant plus support type extension. BREAKING CHANGE: the raw value API has been re-written to be more usable and aligned with the rest of the code base. Signed-off-by: Sergei Trofimov --- comid/cbor.go | 1 + comid/comid_test.go | 2 +- comid/cond_endorse_series_triple_test.go | 46 +-- comid/measurement.go | 2 +- comid/rawvalue.go | 365 ++++++++++++++++++----- comid/rawvalue_test.go | 299 ++++++++++++++----- comid/test_vars.go | 4 +- comid/typeandvalue.go | 14 - profiles/cca/platform.go | 6 - profiles/cca/realm.go | 6 - profiles/cca/realm_test.go | 2 +- 11 files changed, 547 insertions(+), 200 deletions(-) delete mode 100644 comid/typeandvalue.go diff --git a/comid/cbor.go b/comid/cbor.go index 58abdc28..31c71e12 100644 --- a/comid/cbor.go +++ b/comid/cbor.go @@ -31,6 +31,7 @@ var ( 560: TaggedBytes{}, 561: TaggedCertPathThumbprint{}, 562: TaggedPKIXAsn1DerCert{}, + 563: TaggedMaskedRawValue{}, 564: TaggedRawIntRange{}, } ) diff --git a/comid/comid_test.go b/comid/comid_test.go index a65f407f..ff68aa77 100644 --- a/comid/comid_test.go +++ b/comid/comid_test.go @@ -51,7 +51,7 @@ func Test_Comid_ToJSONPretty(t *testing.T) { }, Measurements: *NewMeasurements().Add(&Measurement{ Val: Mval{ - RawValue: NewRawValue().SetBytes(MustHexDecode(t, "deadbeef")), + RawValue: NewRawValueFromBytes(MustHexDecode(t, "deadbeef")), }, }), }), diff --git a/comid/cond_endorse_series_triple_test.go b/comid/cond_endorse_series_triple_test.go index 91551350..83d6f471 100644 --- a/comid/cond_endorse_series_triple_test.go +++ b/comid/cond_endorse_series_triple_test.go @@ -124,7 +124,7 @@ func Test_CondEndorseSeriesTriple_Valid_ValidSeries(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -132,7 +132,7 @@ func Test_CondEndorseSeriesTriple_Valid_ValidSeries(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -149,7 +149,7 @@ func Test_CondEndorseSeriesTriple_Valid_ValidSeries(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(789), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("condition-value")), + RawValue: NewRawValueFromBytes([]byte("condition-value")), }, }, ), @@ -223,7 +223,7 @@ func Test_CondEndorseSeriesTriple_RegisterExtensions_SeriesError(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -240,7 +240,7 @@ func Test_CondEndorseSeriesRecord_RegisterExtensions_OK(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -248,7 +248,7 @@ func Test_CondEndorseSeriesRecord_RegisterExtensions_OK(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -270,7 +270,7 @@ func Test_CondEndorseSeriesRecord_RegisterExtensions_SelectionError(t *testing.T &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -289,7 +289,7 @@ func Test_CondEndorseSeriesRecord_RegisterExtensions_AdditionError(t *testing.T) &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -307,7 +307,7 @@ func Test_CondEndorseSeriesRecords_RegisterExtensions_OK(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -315,7 +315,7 @@ func Test_CondEndorseSeriesRecords_RegisterExtensions_OK(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -338,7 +338,7 @@ func Test_CondEndorseSeriesRecords_RegisterExtensions_Error(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -346,7 +346,7 @@ func Test_CondEndorseSeriesRecords_RegisterExtensions_Error(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -464,7 +464,7 @@ func Test_CondEndorseSeriesRecord_Valid_OK(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -472,7 +472,7 @@ func Test_CondEndorseSeriesRecord_Valid_OK(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -488,7 +488,7 @@ func Test_CondEndorseSeriesRecord_Valid_EmptySelections(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -503,7 +503,7 @@ func Test_CondEndorseSeriesRecord_Valid_EmptyAdditions(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -547,7 +547,7 @@ func Test_CondEndorseSeriesRecord_GetExtensions(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -555,7 +555,7 @@ func Test_CondEndorseSeriesRecord_GetExtensions(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -578,7 +578,7 @@ func Test_CondEndorseSeriesRecords_Add_Valid(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -586,7 +586,7 @@ func Test_CondEndorseSeriesRecords_Add_Valid(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -611,7 +611,7 @@ func Test_CondEndorseSeriesRecords_Valid_InvalidRecord(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), @@ -630,7 +630,7 @@ func Test_CondEndorseSeriesRecords_GetExtensions(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(123), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("test-value")), + RawValue: NewRawValueFromBytes([]byte("test-value")), }, }, ), @@ -638,7 +638,7 @@ func Test_CondEndorseSeriesRecords_GetExtensions(t *testing.T) { &Measurement{ Key: MustNewMkey(uint64(456), UintType), Val: Mval{ - RawValue: NewRawValue().SetBytes([]byte("add-value")), + RawValue: NewRawValueFromBytes([]byte("add-value")), }, }, ), diff --git a/comid/measurement.go b/comid/measurement.go index 1ca5abfa..dfeec82d 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -618,7 +618,7 @@ func (o *Measurement) SetVersion(ver string, scheme int64) *Measurement { // measurement-values-map of the target measurement func (o *Measurement) SetRawValueBytes(rawValue, rawValueMask []byte) *Measurement { if o != nil { - o.Val.RawValue = NewRawValue().SetBytes(rawValue) + o.Val.RawValue = NewRawValueFromBytes(rawValue) if len(rawValueMask) != 0 { o.Val.RawValueMask = &rawValueMask } diff --git a/comid/rawvalue.go b/comid/rawvalue.go index 69e11c4a..a6c102f8 100644 --- a/comid/rawvalue.go +++ b/comid/rawvalue.go @@ -1,4 +1,4 @@ -// Copyright 2021-2025 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -6,133 +6,340 @@ package comid import ( "bytes" "encoding/json" + "errors" "fmt" - "log" - "reflect" + + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" ) -// RawValue models a $raw-value-type-choice. For now, the only available type is bytes. -type RawValue struct { - val interface{} -} +const MaskedType = "masked" -func NewRawValue() *RawValue { - return &RawValue{} +// IRawValueValue is the interface implemented by concrete RawValue value +// types. +type IRawValueValue interface { + extensions.ITypeChoiceValue + + Bytes() []byte } -func (o *RawValue) SetBytes(val []byte) *RawValue { - if o != nil { - o.val = TaggedBytes(val) +// NewRawValue returns the pointer to a new RawValue of the specified type, +// constructed using the provided value b. The type of b depends on the +// specified raw value type. For a bytes value, it must be a []byte, for +// masked bytes, it must be a [2][]byte, [][]byte of length 2, or a []byte (in +// which case the mast will be a value of the same length with all bits set). +func NewRawValue(b any, typ string) (*RawValue, error) { + factory, ok := rawValueValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unexpected RawValue type: %s", typ) } - return o + + return factory(b) } -func (o RawValue) GetBytes() ([]byte, error) { - if o.val == nil { - return nil, fmt.Errorf("raw value is not set") +// MustNewRawValue is the same as NewRawValues, but it panics on error. +func MustNewRawValue(b any, typ string) *RawValue { + ret, err := NewRawValue(b, typ) + if err != nil { + panic(err) } - switch t := o.val.(type) { - case TaggedBytes: - return []byte(t), nil - default: - return nil, fmt.Errorf("unknown type %T for $raw-value-type-choice", t) + return ret +} + +// NewRawValuesFromBytes returns a pointer to a new RawValue with the specified +// bytes as the underlying value. +func NewRawValueFromBytes(b []byte) *RawValue { + ret, err := NewBytesRawValue(b) + if err != nil { + // cannot happen + panic(err) } + + return ret } -// Equal confirms if the RawValue instances are equal -func (o RawValue) Equal(r RawValue) bool { - return reflect.DeepEqual(o, r) +// NewRawValueWithMask returns a pointer to a new RawValues with a masked +// underlying values created from the specified value and mask. An error is +// returned if the length of the mask does not match that of the value. +func NewRawValueWithMask(val []byte, mask []byte) (*RawValue, error) { + return NewMaskedRawValue([2][]byte{val, mask}) } -// CompareAgainstReference checks if a RawValue object matches with a reference -// -// See section-8.9.6.1.4 in the IETF CoRIM spec for the rules to compare a -// RawValue object against a reference. -func (o RawValue) CompareAgainstReference(ref []byte, mask *[]byte) bool { - claim, err := o.GetBytes() +// MustNewRawValueWithMask is like NewRawValueWithMask but panics if the value +// and mask are of different lengths. +func MustNewRawValueWithMask(val []byte, mask []byte) *RawValue { + ret, err := NewRawValueWithMask(val, mask) if err != nil { - log.Printf("RawValue:CompareAgainstReference: Error: %v", err) - return false + panic(err) } - if mask != nil { - if len(claim) != len(ref) { - return false - } + return ret +} - if len(*mask) != len(claim) { - log.Printf("RawValue:CompareAgainstReference: Error: mask length") - return false - } +// RawValue models a $raw-value-type-choice. For now, the only available type is bytes. +type RawValue struct { + Value IRawValueValue +} - for i := range *mask { - claim[i] = (*mask)[i] & claim[i] - ref[i] = (*mask)[i] & ref[i] - } +// Type returns the type of the underlying value +func (o RawValue) Type() string { + return o.Value.Type() +} + +// Bytes returns the bytes of the underlying value +func (o RawValue) Bytes() []byte { + return o.Value.Bytes() +} + +// Mask returns the mask of the underlying value. If the underlying value is +// not masked, nil is returned. +func (o RawValue) Mask() []byte { + masked, ok := o.Value.(interface{ Mask() []byte }) + if ok { + return masked.Mask() } - return bytes.Equal(claim, ref) + return nil +} + +// Equal returns true if both raw values are equal, or false if they are not or +// of one or more of RawValues is invalid. If both values specify masks and +// masks differ, false is returned (even if bytes masked with respective masks +// equal). +func (o RawValue) Equal(other *RawValue) bool { + ret, _ := maskedEqual(o.Bytes(), o.Mask(), other.Bytes(), other.Mask()) + return ret +} + +// CompareAgainstReference checks if a RawValue object matches with a reference +// +// (draft-ietf-rats-corim §9.4.6.1.4). +func (o RawValue) CompareAgainstReference(ref []byte, mask []byte) bool { + ret, _ := maskedEqual(o.Bytes(), o.Mask(), ref, mask) + return ret } func (o RawValue) MarshalCBOR() ([]byte, error) { - return em.Marshal(o.val) + return em.Marshal(o.Value) } func (o *RawValue) UnmarshalCBOR(data []byte) error { - var rawValue TaggedBytes + return dm.Unmarshal(data, &o.Value) +} + +func (o RawValue) MarshalJSON() ([]byte, error) { + valueBytes, err := json.Marshal(o.Value) + if err != nil { + return nil, err + } - if dm.Unmarshal(data, &rawValue) == nil { - o.val = rawValue - return nil + value := encoding.TypeAndValue{ + Type: o.Value.Type(), + Value: valueBytes, } - return fmt.Errorf("unknown raw-value (CBOR: %x)", data) + return json.Marshal(value) } -// UnmarshalJSON deserializes the type'n'value JSON object into the target RawValue. -// The only supported type is BytesType with value func (o *RawValue) UnmarshalJSON(data []byte) error { - var v tnv + var value encoding.TypeAndValue + if err := json.Unmarshal(data, &value); err != nil { + return err + } - if err := json.Unmarshal(data, &v); err != nil { + decoded, err := NewRawValue(nil, value.Type) + if err != nil { return err } - switch v.Type { - case BytesType: - var x []byte - if err := json.Unmarshal(v.Value, &x); err != nil { - return fmt.Errorf( - "cannot unmarshal $raw-value-type-choice of type bytes: %w", - err, - ) - } - o.val = TaggedBytes(x) + if err := json.Unmarshal(value.Value, &decoded.Value); err != nil { + return err + } + + o.Value = decoded.Value + + return nil +} + +// NewBytesRawValue creates a new RawValue with the underlying value being +// TaggedBytes constructed from the provided input. +func NewBytesRawValue(val any) (*RawValue, error) { + if val == nil { + return &RawValue{&TaggedBytes{}}, nil + } + + switch t := val.(type) { + case []byte: + return &RawValue{(*TaggedBytes)(&t)}, nil + case TaggedBytes: + return &RawValue{&t}, nil + case *TaggedBytes: + return &RawValue{t}, nil default: - return fmt.Errorf("unknown type %s for $raw-value-type-choice", v.Type) + return nil, fmt.Errorf("value must be a byte slice; found %T", t) + } +} + +// TaggedMaskedRawValue is a RawValue type that combines a value with a mask. +// The mask must be of the same length as the value. When this is being +// compared with an unmasked RawValue, only the masked bits of both underlying +// values are compared. +type TaggedMaskedRawValue struct { + _ struct{} `cbor:",toarray"` + Value []byte `json:"value"` + MaskBytes []byte `json:"mask"` +} + +func (o TaggedMaskedRawValue) Type() string { + return MaskedType +} + +func (o TaggedMaskedRawValue) Bytes() []byte { + return o.Value +} + +func (o TaggedMaskedRawValue) Mask() []byte { + return o.MaskBytes +} + +func (o TaggedMaskedRawValue) String() string { + return string(o.Value) +} + +func (o TaggedMaskedRawValue) Valid() error { + if len(o.Value) == 0 { + return errors.New("value not set") + } + + if len(o.MaskBytes) != len(o.Value) { + return errors.New("mask and value lengths differ") } return nil } -func (o RawValue) MarshalJSON() ([]byte, error) { - var ( - v tnv - b []byte - err error - ) +// NewMaskedRawValue returns a new RawValue with the underlying value and mask +// constructed from the provided input. If the input is a [2][]byte or a +// [][]byte of length 2, then the first element is used as the value, and the +// second element as the mask. If the input is a []byte, then that is used as +// the value and the mask is a []byte of the same length with all bits set. +func NewMaskedRawValue(val any) (*RawValue, error) { + if val == nil { + return &RawValue{&TaggedMaskedRawValue{}}, nil + } - switch t := o.val.(type) { + switch t := val.(type) { + case []byte: + return &RawValue{&TaggedMaskedRawValue{Value: t, MaskBytes: allSet(len(t))}}, nil case TaggedBytes: - b, err = json.Marshal(o.val) - if err != nil { - return nil, err + return &RawValue{&TaggedMaskedRawValue{Value: t, MaskBytes: allSet(len(t))}}, nil + case *TaggedBytes: + return &RawValue{&TaggedMaskedRawValue{Value: *t, MaskBytes: allSet(len(*t))}}, nil + case [2][]byte: + return &RawValue{&TaggedMaskedRawValue{Value: t[0], MaskBytes: t[1]}}, nil + case [][]byte: + if len(t) != 2 { + return nil, errors.New("[][]byte must contain exactly two elements") } - v = tnv{Type: BytesType, Value: b} + return &RawValue{&TaggedMaskedRawValue{Value: t[0], MaskBytes: t[1]}}, nil default: - return nil, fmt.Errorf("unknown type %T for raw-value-type-choice", t) + return nil, fmt.Errorf("value must be a byte slice; found %T", t) + } +} + +// MustNewMaskedRawValue is like NewMaskedRawValue but panics on error. +func MustNewMaskedRawValue(val any) *RawValue { + ret, err := NewMaskedRawValue(val) + if err != nil { + panic(err) + } + + return ret +} + +// IRawValueFactory defines the signature for the factory functions that may be +// registred using RegisterRawValueType to provide a new implementation of the +// corresponding type choice. The factory function should create a new +// *RawValue with the underlying value created based on the provided input. The +// range of valid inputs is up to the specific type choice implementation, +// however it _must_ accept nil as one of the inputs, and return the Zero value +// for the implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IRawValueFactory func(any) (*RawValue, error) + +var rawValueValueRegister = map[string]IRawValueFactory{ + BytesType: NewBytesRawValue, + MaskedType: NewMaskedRawValue, +} + +// RegisterRawValueType registers a new IRawValueValue implementation +// (created by the provided IRawValueFactory) under the specified type name +// and CBOR tag. +func RegisterRawValueType(tag uint64, factory IRawValueFactory) error { + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Type() + if _, exists := rawValueValueRegister[typ]; exists { + return fmt.Errorf("raw value type with name %q already exists", typ) + } + + if err := registerCOMIDTag(tag, nilVal.Value); err != nil { + return err + } + + rawValueValueRegister[typ] = factory + + return nil +} + +func allSet(n int) []byte { + ret := make([]byte, n) + for i := range ret { + ret[i] = 0xff + } + + return ret +} + +func maskedEqual(lhs []byte, lhsMask []byte, rhs []byte, rhsMask []byte) (bool, error) { + if lhsMask != nil && rhsMask != nil && !bytes.Equal(lhsMask, rhsMask) { + return false, errors.New("LHS and RHS masks are both set and are unequal") + } + + if lhsMask != nil && len(lhsMask) != len(lhs) { + return false, errors.New("LHS mask and value lengths differ") + } + + if rhsMask != nil && len(rhsMask) != len(rhs) { + return false, errors.New("RHS mask and value lengths differ") + } + + if len(lhs) != len(rhs) { + return false, nil + } + + mask := lhsMask + if rhsMask != nil { + mask = rhsMask + } + + if mask != nil { + lhs = applyMask(lhs, mask) + rhs = applyMask(rhs, mask) + } + + return bytes.Equal(lhs, rhs), nil +} + +func applyMask(val, mask []byte) []byte { + ret := make([]byte, len(val)) + for i, b := range val { + ret[i] = b & mask[i] } - return json.Marshal(v) + return ret } diff --git a/comid/rawvalue_test.go b/comid/rawvalue_test.go index a0cf3617..c7e1b634 100644 --- a/comid/rawvalue_test.go +++ b/comid/rawvalue_test.go @@ -1,4 +1,4 @@ -// Copyright 2024-2025 Contributors to the Veraison project. +// Copyright 2024-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -6,88 +6,253 @@ import ( "testing" "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) -func TestRawValue_NewRawValue_ok(t *testing.T) { - tv := NewRawValue() - require.NotNil(t, tv) -} +func TestRawValue_NewRawValue(t *testing.T) { + testCases := []struct { + title string + val any + typ string + expected *RawValue + err string + }{ + { + title: "ok bytes from []byte", + val: []byte{0x01, 0x02, 0x03}, + typ: "bytes", + expected: &RawValue{ + Value: &TaggedBytes{0x01, 0x02, 0x03}, + }, + }, + { + title: "ok bytes from TaggedBytes", + val: TaggedBytes{0x01, 0x02, 0x03}, + typ: "bytes", + expected: &RawValue{ + Value: &TaggedBytes{0x01, 0x02, 0x03}, + }, + }, + { + title: "ok bytes from *TaggedBytes", + val: &TaggedBytes{0x01, 0x02, 0x03}, + typ: "bytes", + expected: &RawValue{ + Value: &TaggedBytes{0x01, 0x02, 0x03}, + }, + }, + { + title: "ok masked from []byte", + val: []byte{0x01, 0x02, 0x03}, + typ: "masked", + expected: &RawValue{ + Value: &TaggedMaskedRawValue{ + Value: []byte{0x01, 0x02, 0x03}, + MaskBytes: []byte{0xFF, 0xFF, 0xFF}, + }, + }, + }, + { + title: "ok masked from TaggedBytes", + val: TaggedBytes{0x01, 0x02, 0x03}, + typ: "masked", + expected: &RawValue{ + Value: &TaggedMaskedRawValue{ + Value: []byte{0x01, 0x02, 0x03}, + MaskBytes: []byte{0xFF, 0xFF, 0xFF}, + }, + }, + }, + { + title: "ok masked from *TaggedBytes", + val: &TaggedBytes{0x01, 0x02, 0x03}, + typ: "masked", + expected: &RawValue{ + Value: &TaggedMaskedRawValue{ + Value: []byte{0x01, 0x02, 0x03}, + MaskBytes: []byte{0xFF, 0xFF, 0xFF}, + }, + }, + }, + { + title: "ok masked from [2][]byte", + val: [2][]byte{{0x01, 0x02, 0x03}, {0x04, 0x05, 0x06}}, + typ: "masked", + expected: &RawValue{ + Value: &TaggedMaskedRawValue{ + Value: []byte{0x01, 0x02, 0x03}, + MaskBytes: []byte{0x04, 0x05, 0x06}, + }, + }, + }, + { + title: "ok masked from [][]byte (len 2)", + val: [][]byte{{0x01, 0x02, 0x03}, {0x04, 0x05, 0x06}}, + typ: "masked", + expected: &RawValue{ + Value: &TaggedMaskedRawValue{ + Value: []byte{0x01, 0x02, 0x03}, + MaskBytes: []byte{0x04, 0x05, 0x06}, + }, + }, + }, + { + title: "bad bytes from string", + val: "foo", + typ: "bytes", + err: "value must be a byte slice", + }, + { + title: "bad masked from string", + val: "foo", + typ: "masked", + err: "value must be a byte slice", + }, + { + title: "bad masked from [][]byte (len 1)", + val: [][]byte{{0x01, 0x02, 0x03}}, + typ: "masked", + err: "[][]byte must contain exactly two elements", + }, + } -func TestRawValue_Set_Get_Bytes_ok(t *testing.T) { - tv := RawValue{} - expected := []byte{0x01, 0x02, 0x03} - rv := tv.SetBytes([]byte{0x01, 0x02, 0x03}) - require.NotNil(t, rv) - rval, err := rv.GetBytes() - assert.NoError(t, err) - assert.Equal(t, expected, rval) -} + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + ret, err := NewRawValue(tc.val, tc.typ) + if tc.err == "" { + assert.NoError(t, err) + assert.EqualValues(t, tc.expected, ret) + } else { + assert.ErrorContains(t, err, tc.err) + } -func TestRawValue_Get_Bytes_nok(t *testing.T) { - rv := RawValue{} - expectedErr := "raw value is not set" - _, err := rv.GetBytes() - assert.EqualError(t, err, expectedErr) - rv = RawValue{"testraw"} - expectedErr = "unknown type string for $raw-value-type-choice" - _, err = rv.GetBytes() - assert.EqualError(t, err, expectedErr) + }) + } } -func TestRawValue_Marshal_UnMarshal_JSON_ok(t *testing.T) { - tv := RawValue{} - rv := tv.SetBytes([]byte{0x01, 0x02, 0x03}) - bytes, err := rv.MarshalJSON() - assert.NoError(t, err) - sv := RawValue{} - err = sv.UnmarshalJSON(bytes) - assert.NoError(t, err) - assert.Equal(t, *rv, sv) -} +func TestRawValue_round_trip(t *testing.T) { + testCases := []struct { + title string + val *RawValue + expectedJSON string + expectedCBOR []byte + }{ + { + title: "bytes", + val: NewRawValueFromBytes([]byte{0x01, 0x02, 0x03}), + expectedJSON: `{"type":"bytes","value":"AQID"}`, + expectedCBOR: []byte{ + 0xd9, 0x02, 0x30, // tag(560) [bytes] + 0x43, // . bstr(3) + 0x01, 0x02, 0x03, + }, + }, + { + title: "masked", + val: MustNewRawValueWithMask( + []byte{0x01, 0x02, 0x03}, + []byte{0x04, 0x05, 0x06}, + ), + expectedJSON: `{"type":"masked","value":{"value":"AQID","mask":"BAUG"}}`, + expectedCBOR: []byte{ + 0xd9, 0x02, 0x33, // tag(563) [masked-raw-value] + 0x82, // . array(2) [masked-raw-value] + 0x43, // . . [0]bstr(3) + 0x01, 0x02, 0x03, + 0x43, // . . [1]bstr(3) + 0x04, 0x05, 0x06, + }, + }, + } -func TestRawValue_Marshal_UnMarshal_CBOR_ok(t *testing.T) { - tv := RawValue{} - rv := tv.SetBytes([]byte{0x01, 0x02, 0x03}) - bytes, err := rv.MarshalCBOR() - assert.NoError(t, err) - sv := RawValue{} - err = sv.UnmarshalCBOR(bytes) - assert.NoError(t, err) - assert.Equal(t, *rv, sv) -} + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + bytes, err := tc.val.MarshalJSON() + assert.NoError(t, err) + assert.JSONEq(t, tc.expectedJSON, string(bytes)) + + var decoded RawValue + err = decoded.UnmarshalJSON(bytes) + assert.NoError(t, err) + assert.Equal(t, tc.val, &decoded) -func TestRawValue_Equal_True(t *testing.T) { - claim := RawValue{} - claim.SetBytes([]byte{0x01, 0x02, 0x03}) - ref := RawValue{} - ref.SetBytes([]byte{0x01, 0x02, 0x03}) + bytes, err = tc.val.MarshalCBOR() + assert.NoError(t, err) + assert.Equal(t, tc.expectedCBOR, bytes) - assert.True(t, claim.Equal(ref)) + err = decoded.UnmarshalCBOR(bytes) + assert.NoError(t, err) + assert.Equal(t, tc.val, &decoded) + }) + } } -func TestRawValue_Equal_False(t *testing.T) { - claim := RawValue{} - claim.SetBytes([]byte{0x01, 0x02, 0x03}) - ref := RawValue{} - ref.SetBytes([]byte{0x01, 0x02, 0x04}) +func TestRawValue_unmarshal_bad(t *testing.T) { + var rv RawValue + + err := rv.UnmarshalJSON([]byte(`"foo"`)) + assert.ErrorContains(t, err, "cannot unmarshal string into Go value of type struct") + + err = rv.UnmarshalJSON([]byte(`{"type":"foo", "value":"bar"}`)) + assert.ErrorContains(t, err, "unexpected RawValue type: foo") + + err = rv.UnmarshalJSON([]byte(`{"type":"bytes", "value":2}`)) + assert.ErrorContains(t, err, "cannot unmarshal number into Go value of type comid.TaggedBytes") + + err = rv.UnmarshalJSON([]byte(`{"type":"masked", "value":"AQID"}`)) + assert.ErrorContains(t, err, "cannot unmarshal string into Go value of type comid.TaggedMaskedRawValue") + + err = rv.UnmarshalCBOR(MustHexDecode(t, "ff")) + assert.ErrorContains(t, err, "unexpected \"break\" code") + + err = rv.UnmarshalCBOR(MustHexDecode(t, "d9023001")) + assert.ErrorContains(t, err, "cannot unmarshal positive integer into Go value of type comid.TaggedBytes") - assert.False(t, claim.Equal(ref)) + rv.Value = nil + err = rv.UnmarshalCBOR(MustHexDecode(t, "d9023343010203")) + assert.ErrorContains(t, err, + "cannot unmarshal byte string into Go value of type comid.TaggedMaskedRawValue") + + err = rv.UnmarshalCBOR(MustHexDecode(t, "d90233824301020301")) + assert.ErrorContains(t, err, + "cannot unmarshal positive integer into Go struct field *comid.TaggedMaskedRawValue.mask") } -func TestRawValue_Compare_True(t *testing.T) { - claim := RawValue{} - claim.SetBytes([]byte{0x01, 0x02, 0x03}) - ref := []byte{0x01, 0x00, 0x03} - mask := []byte{0xff, 0x00, 0xff} +func TestRawValue_Equal(t *testing.T) { + brv := NewRawValueFromBytes([]byte{0x01, 0x02, 0x03}) + mrv := MustNewMaskedRawValue([]byte{0x01, 0x02, 0x03}) + + assert.True(t, brv.Equal(mrv)) + assert.True(t, mrv.Equal(brv)) + + mrv = MustNewMaskedRawValue([]byte{0x11, 0x12, 0x13}) + + assert.False(t, brv.Equal(mrv)) + assert.False(t, mrv.Equal(brv)) + + mrv = MustNewRawValueWithMask([]byte{0x11, 0x12, 0x13}, []byte{0x0F, 0x0F, 0x0F}) - assert.True(t, claim.CompareAgainstReference(ref, &mask)) + assert.True(t, brv.Equal(mrv)) + assert.True(t, mrv.Equal(brv)) + + mrv2 := MustNewRawValueWithMask([]byte{0xA1, 0xB2, 0xC3}, []byte{0x0F, 0x0F, 0x0F}) + + assert.True(t, mrv2.Equal(mrv)) + assert.True(t, mrv.Equal(mrv2)) + + mrv2 = MustNewRawValueWithMask([]byte{0x11, 0x12, 0x13}, []byte{0x07, 0x07, 0x07}) + + assert.False(t, mrv2.Equal(mrv)) + assert.False(t, mrv.Equal(mrv2)) } -func TestRawValue_Compare_False(t *testing.T) { - claim := RawValue{} - claim.SetBytes([]byte{0x01, 0x02, 0x03}) - ref := []byte{0x04, 0x05, 0x06} +func TestRawValue_CompareAgainstReference(t *testing.T) { + brv := NewRawValueFromBytes([]byte{0x01, 0x02, 0x03}) + + assert.True(t, brv.CompareAgainstReference([]byte{0x01, 0x02, 0x03}, nil)) + assert.False(t, brv.CompareAgainstReference([]byte{0x11, 0x12, 0x13}, nil)) + assert.True(t, brv.CompareAgainstReference([]byte{0x11, 0x12, 0x13}, []byte{0x0F, 0x0F, 0x0F})) - assert.False(t, claim.CompareAgainstReference(ref, nil)) + mrv := MustNewRawValueWithMask([]byte{0x01, 0x02, 0x03}, []byte{0x0F, 0x0F, 0x0F}) + assert.True(t, mrv.CompareAgainstReference([]byte{0x11, 0x12, 0x13}, []byte{0x0F, 0x0F, 0x0F})) + assert.False(t, mrv.CompareAgainstReference([]byte{0x11, 0x12, 0x13}, []byte{0x07, 0x07, 0x07})) } diff --git a/comid/test_vars.go b/comid/test_vars.go index af6bb8de..2f5b96d5 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -277,7 +277,7 @@ func NewTestComid(t *testing.T) *Comid { }, Measurements: *NewMeasurements().Add(&Measurement{ Val: Mval{ - RawValue: NewRawValue().SetBytes(MustHexDecode(t, "deadbeef")), + RawValue: NewRawValueFromBytes(MustHexDecode(t, "deadbeef")), }, }), }), @@ -287,7 +287,7 @@ func NewTestComid(t *testing.T) *Comid { }, Measurements: *NewMeasurements().Add(&Measurement{ Val: Mval{ - RawValue: NewRawValue().SetBytes(MustHexDecode(t, "deadbeef")), + RawValue: NewRawValueFromBytes(MustHexDecode(t, "deadbeef")), }, }), }), diff --git a/comid/typeandvalue.go b/comid/typeandvalue.go deleted file mode 100644 index 3f987d45..00000000 --- a/comid/typeandvalue.go +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright 2021 Contributors to the Veraison project. -// SPDX-License-Identifier: Apache-2.0 - -package comid - -import "encoding/json" - -// tnv (type'n'value) stores a JSON object with two attributes: a string "type" -// and a generic "value" (left undecoded) defined by type. This type is used in -// a few places to implement the choice types that CBOR handles using tags. -type tnv struct { - Type string `json:"type"` - Value json.RawMessage `json:"value"` -} diff --git a/profiles/cca/platform.go b/profiles/cca/platform.go index 8933e23c..902a616a 100644 --- a/profiles/cca/platform.go +++ b/profiles/cca/platform.go @@ -195,12 +195,6 @@ func validateCCAPlatformConfig(measurement *comid.Measurement) error { return fmt.Errorf("raw-value-mask is mandatory for cca.platform-config") } - // Validate we can extract bytes from raw-value - _, err := measurement.Val.RawValue.GetBytes() - if err != nil { - return fmt.Errorf("unable to extract bytes from raw-value: %w", err) - } - return nil } diff --git a/profiles/cca/realm.go b/profiles/cca/realm.go index beba99e1..0a938410 100644 --- a/profiles/cca/realm.go +++ b/profiles/cca/realm.go @@ -191,11 +191,5 @@ func validateCCARealmPersonalizationValue(measurement *comid.Measurement) error return fmt.Errorf("raw-value is mandatory for cca.rpv") } - // Validate we can extract bytes from raw-value - _, err := measurement.Val.RawValue.GetBytes() - if err != nil { - return fmt.Errorf("unable to extract bytes from raw-value: %w", err) - } - return nil } diff --git a/profiles/cca/realm_test.go b/profiles/cca/realm_test.go index 6f34a1e9..64271d8f 100644 --- a/profiles/cca/realm_test.go +++ b/profiles/cca/realm_test.go @@ -62,7 +62,7 @@ func mustNewCCARealmRPVMeasurement(data []byte) *comid.Measurement { } // Set raw-value as tagged bytes - rv := comid.NewRawValue().SetBytes(data) + rv := comid.NewRawValueFromBytes(data) measurement.Val.RawValue = rv return measurement From 38a1d3ee9558a23efab585a52d5c7523098b58e7 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 15 May 2026 09:57:02 +0100 Subject: [PATCH 09/18] feat(comid): add int-range to measurement value - Add MustNewRawInteger, a panicking version of NewRawInteger. - Allow int as input into NewRawIntInteger (only int64 was accepted before). - Add int-range (code point 15) to Mval. Signed-off-by: Sergei Trofimov --- comid/measurement.go | 10 +++++++++- comid/measurement_test.go | 12 ++++++++++++ comid/rawint.go | 12 ++++++++++++ 3 files changed, 33 insertions(+), 1 deletion(-) diff --git a/comid/measurement.go b/comid/measurement.go index dfeec82d..97a22b81 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -373,6 +373,7 @@ type Mval struct { Name *string `cbor:"11,keyasint,omitempty" json:"name,omitempty"` CryptoKeys *CryptoKeys `cbor:"13,keyasint,omitempty" json:"cryptokeys,omitempty"` IntegrityRegisters *IntegrityRegisters `cbor:"14,keyasint,omitempty" json:"integrity-registers,omitempty"` + IntRange *RawInt `cbor:"15,keyasint,omitempty" json:"int-range,omitempty"` Extensions } @@ -444,7 +445,7 @@ func (o Mval) MarshalJSON() ([]byte, error) { } // Valid returns an error if none of the measurement values are set and the Extensions are empty. -// nolint:gocritic +// nolint:gocritic,gocyclo func (o Mval) Valid() error { // Check if no measurement values are set if o.Ver == nil && @@ -461,6 +462,7 @@ func (o Mval) Valid() error { o.Name == nil && o.CryptoKeys == nil && o.IntegrityRegisters == nil && + o.IntRange == nil && o.IsEmpty() { return fmt.Errorf("no measurement value set") @@ -511,6 +513,12 @@ func (o Mval) Valid() error { } } + if o.IntRange != nil { + if err := o.IntRange.Valid(); err != nil { + return err + } + } + // raw value and raw-value-mask have no specific semantics here // Validate extensions (custom logic implemented in validMval()) diff --git a/comid/measurement_test.go b/comid/measurement_test.go index d996af2e..df136c06 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -577,6 +577,18 @@ func TestMval_Valid(t *testing.T) { assert.ErrorContains(t, err, "digest at index 0") }) + t.Run("IntRange valid", func(t *testing.T) { + mval := Mval{IntRange: MustNewRawInt(1, RawIntIntegerType)} + err := mval.Valid() + assert.NoError(t, err) + }) + + t.Run("IntRange invalid", func(t *testing.T) { + mval := Mval{IntRange: &RawInt{}} + err := mval.Valid() + assert.ErrorContains(t, err, "RawInt value unset") + }) + t.Run("Extensions valid", func(t *testing.T) { // Suppose we have some extension data that is considered valid ext := Extensions{} diff --git a/comid/rawint.go b/comid/rawint.go index 7eb9eaea..c4739047 100644 --- a/comid/rawint.go +++ b/comid/rawint.go @@ -40,6 +40,16 @@ func NewRawInt(val any, typ string) (*RawInt, error) { return factory(val) } +// MustNewRawInt is like NewRawInt but panics on error. +func MustNewRawInt(val any, typ string) *RawInt { + ret, err := NewRawInt(val, typ) + if err != nil { + panic(err) + } + + return ret +} + // IsSet confirms if RawInt has a value or if it's empty func (o RawInt) IsSet() bool { return o.Value != nil } @@ -162,6 +172,8 @@ func NewRawIntInteger(val any) (*RawIntInteger, error) { ret = *v case int64: ret = RawIntInteger(v) + case int: + ret = RawIntInteger(v) default: return nil, fmt.Errorf("unexpected type for RawIntInteger: %T", v) } From ad4a05bc08a600fa96db7aa7398defdeb2421f8b Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 15 May 2026 12:59:24 +0100 Subject: [PATCH 10/18] fix(comid): CryptoKey bytes JSON serialization Serialize the bytes variant as base64 strings. This is consistent with how bytes are serialized elsewhere. This fixes CryptoKey serialization more generally, making it consistent with how serialization is handled for other type-extended types; i.e. by relying on factory functions constructing the zero value for the type when given nil as input. Signed-off-by: Sergei Trofimov --- comid/cryptokey.go | 71 ++++++++++++++++++++++++++++++----------- comid/cryptokey_test.go | 58 +++++++++++++++++++++++---------- 2 files changed, 93 insertions(+), 36 deletions(-) diff --git a/comid/cryptokey.go b/comid/cryptokey.go index 2c1f4eb7..a7850211 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -109,7 +109,7 @@ func (o CryptoKey) PublicKey() (crypto.PublicKey, error) { // MarshalJSON returns a []byte containing the JSON representation of the // CryptoKey. func (o CryptoKey) MarshalJSON() ([]byte, error) { - valueBytes, err := json.Marshal(o.Value.String()) + valueBytes, err := json.Marshal(o.Value) if err != nil { return nil, err } @@ -135,22 +135,16 @@ func (o *CryptoKey) UnmarshalJSON(b []byte) error { return errors.New("key type not set") } - factory, ok := cryptoKeyValueRegister[value.Type] - if !ok { - return fmt.Errorf("unexpected ICryptoKeyValue type: %q", value.Type) - } - - var valueString string - if err := json.Unmarshal(value.Value, &valueString); err != nil { + dest, err := NewCryptoKey(nil, value.Type) + if err != nil { return err } - k, err := factory(valueString) - if err != nil { + if err := json.Unmarshal(value.Value, &dest.Value); err != nil { return err } - o.Value = k.Value + o.Value = dest.Value return o.Valid() } @@ -183,6 +177,11 @@ type ICryptoKeyValue interface { type TaggedPKIXBase64Key string func NewPKIXBase64Key(k any) (*CryptoKey, error) { + if k == nil { + key := TaggedPKIXBase64Key("") + return &CryptoKey{&key}, nil + } + s, ok := k.(string) if !ok { return nil, fmt.Errorf("value must be a string; found %T", k) @@ -192,7 +191,7 @@ func NewPKIXBase64Key(k any) (*CryptoKey, error) { if err := key.Valid(); err != nil { return nil, err } - return &CryptoKey{key}, nil + return &CryptoKey{&key}, nil } func MustNewPKIXBase64Key(k any) *CryptoKey { @@ -250,6 +249,11 @@ func (o TaggedPKIXBase64Key) PublicKey() (crypto.PublicKey, error) { type TaggedPKIXBase64Cert string func NewPKIXBase64Cert(k any) (*CryptoKey, error) { + if k == nil { + cert := TaggedPKIXBase64Cert("") + return &CryptoKey{&cert}, nil + } + s, ok := k.(string) if !ok { return nil, fmt.Errorf("value must be a string; found %T", k) @@ -259,7 +263,7 @@ func NewPKIXBase64Cert(k any) (*CryptoKey, error) { if err := cert.Valid(); err != nil { return nil, err } - return &CryptoKey{cert}, nil + return &CryptoKey{&cert}, nil } func MustNewPKIXBase64Cert(k any) *CryptoKey { @@ -332,6 +336,11 @@ func (o TaggedPKIXBase64Cert) cert() (*x509.Certificate, error) { type TaggedPKIXBase64CertPath string func NewPKIXBase64CertPath(k any) (*CryptoKey, error) { + if k == nil { + cert := TaggedPKIXBase64CertPath("") + return &CryptoKey{&cert}, nil + } + s, ok := k.(string) if !ok { return nil, fmt.Errorf("value must be a string; found %T", k) @@ -342,7 +351,7 @@ func NewPKIXBase64CertPath(k any) (*CryptoKey, error) { return nil, err } - return &CryptoKey{cert}, nil + return &CryptoKey{&cert}, nil } func MustNewPKIXBase64CertPath(k any) *CryptoKey { @@ -430,7 +439,7 @@ type TaggedCOSEKey []byte func NewCOSEKey(k any) (*CryptoKey, error) { if k == nil { - return &CryptoKey{TaggedCOSEKey{}}, nil + return &CryptoKey{&TaggedCOSEKey{}}, nil } var b []byte @@ -454,7 +463,7 @@ func NewCOSEKey(k any) (*CryptoKey, error) { return nil, err } - return &CryptoKey{key}, nil + return &CryptoKey{&key}, nil } func MustNewCOSEKey(k any) *CryptoKey { @@ -659,7 +668,12 @@ type TaggedThumbprint struct { digest } +// nolint:dupl func NewThumbprint(k any) (*CryptoKey, error) { + if k == nil { + return &CryptoKey{&TaggedThumbprint{}}, nil + } + var he swid.HashEntry var err error @@ -675,7 +689,7 @@ func NewThumbprint(k any) (*CryptoKey, error) { return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) } - key := &CryptoKey{TaggedThumbprint{digest{he}}} + key := &CryptoKey{&TaggedThumbprint{digest{he}}} if err := key.Valid(); err != nil { return nil, err @@ -704,7 +718,12 @@ type TaggedCertThumbprint struct { digest } +// nolint:dupl func NewCertThumbprint(k any) (*CryptoKey, error) { + if k == nil { + return &CryptoKey{&TaggedCertThumbprint{}}, nil + } + var he swid.HashEntry var err error @@ -720,7 +739,7 @@ func NewCertThumbprint(k any) (*CryptoKey, error) { return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) } - key := &CryptoKey{TaggedCertThumbprint{digest{he}}} + key := &CryptoKey{&TaggedCertThumbprint{digest{he}}} if err := key.Valid(); err != nil { return nil, err @@ -750,7 +769,12 @@ type TaggedCertPathThumbprint struct { digest } +// nolint:dupl func NewCertPathThumbprint(k any) (*CryptoKey, error) { + if k == nil { + return &CryptoKey{&TaggedCertPathThumbprint{}}, nil + } + var he swid.HashEntry var err error @@ -766,7 +790,7 @@ func NewCertPathThumbprint(k any) (*CryptoKey, error) { return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) } - key := &CryptoKey{TaggedCertPathThumbprint{digest{he}}} + key := &CryptoKey{&TaggedCertPathThumbprint{digest{he}}} if err := key.Valid(); err != nil { return nil, err @@ -848,3 +872,12 @@ func NewCryptoKeyTaggedBytes(val any) (*CryptoKey, error) { return &CryptoKey{tb}, nil } + +func MustNewCryptoKeyTaggedBytes(val any) *CryptoKey { + ret, err := NewCryptoKeyTaggedBytes(val) + if err != nil { + panic(err) + } + + return ret +} diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index af9cf1ca..f73eaeb3 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -7,6 +7,7 @@ import ( "crypto" "encoding/base64" "encoding/json" + "errors" "fmt" "strings" "testing" @@ -216,7 +217,7 @@ func Test_CryptoKey_JSON_roundtrip(t *testing.T) { { Type: BytesType, In: TestTaggedBytes, - Out: string(TestTaggedBytes), + Out: base64.StdEncoding.EncodeToString(TestTaggedBytes), }, { Type: PKIXBase64KeyType, @@ -263,17 +264,19 @@ func Test_CryptoKey_JSON_roundtrip(t *testing.T) { ), }, } { - key := MustNewCryptoKey(tv.In, tv.Type) - data, err := json.Marshal(key) - require.NoError(t, err) - - expected := fmt.Sprintf(`{"type": %q, "value": %q}`, tv.Type, tv.Out) - assert.JSONEq(t, expected, string(data)) - - var key2 CryptoKey - err = json.Unmarshal(data, &key2) - require.NoError(t, err) - assert.Equal(t, *key, key2) + t.Run(tv.Type, func(t *testing.T) { + key := MustNewCryptoKey(tv.In, tv.Type) + data, err := json.Marshal(key) + require.NoError(t, err) + + expected := fmt.Sprintf(`{"type": %q, "value": %q}`, tv.Type, tv.Out) + assert.JSONEq(t, expected, string(data)) + + var key2 CryptoKey + err = json.Unmarshal(data, &key2) + require.NoError(t, err) + assert.Equal(t, *key, key2) + }) } } @@ -298,15 +301,15 @@ func Test_CryptoKey_UnmarshalJSON_negative(t *testing.T) { }, { Val: `{"type": "cose-key", "value":";;;"}`, - ErrMsg: "base64 decode error", + ErrMsg: "illegal base64 data", }, { Val: `{"type": "thumbprint", "value":"deadbeef"}`, - ErrMsg: "swid.HashEntry decode error", + ErrMsg: "bad format: expecting ;", }, { Val: `{"type": "random-key", "value":"deadbeef"}`, - ErrMsg: "unexpected ICryptoKeyValue type", + ErrMsg: "unexpected CryptoKey type: random-key", }, } { err := key.UnmarshalJSON([]byte(tv.Val)) @@ -435,7 +438,7 @@ func Test_NewCryptoKey_negative(t *testing.T) { type testCryptoKey [4]byte func newTestCryptoKey(_ any) (*CryptoKey, error) { - return &CryptoKey{&testCryptoKey{0x74, 0x64, 0x73, 0x74}}, nil + return &CryptoKey{&testCryptoKey{0x74, 0x65, 0x73, 0x74}}, nil } func (o testCryptoKey) PublicKey() (crypto.PublicKey, error) { @@ -454,6 +457,27 @@ func (o testCryptoKey) Valid() error { return nil } +func (o testCryptoKey) MarshalJSON() ([]byte, error) { + return json.Marshal(o.String()) +} + +func (o *testCryptoKey) UnmarshalJSON(data []byte) error { + var text string + if err := json.Unmarshal(data, &text); err != nil { + return err + } + + if len(text) != 4 { + return errors.New("wrong length") + } + + for i, b := range []byte(text) { + o[i] = b + } + + return nil +} + func Test_RegisterCryptoKey(t *testing.T) { err := RegisterCryptoKeyType(99998, newTestCryptoKey) require.NoError(t, err) @@ -475,7 +499,7 @@ func Test_RegisterCryptoKey(t *testing.T) { assert.Equal(t, data, []byte{ 0xda, 0x0, 0x1, 0x86, 0x9e, // tag 99998 0x44, // bstr(4) - 0x74, 0x64, 0x73, 0x74, // "test" + 0x74, 0x65, 0x73, 0x74, // "test" }) var out2 CryptoKey From e30261b45871a1e8457c7a141b9cda34f823b667 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 15 May 2026 11:13:51 +0100 Subject: [PATCH 11/18] feat(comid): add Conditions to KeyTriples Add conditions field to KeyTriple, which may be combined with the Environment field to identify the Target Environment the triple relates to. This field was first introduced in rev. 7 of the spec. Signed-off-by: Sergei Trofimov --- comid/cond_endorse_series_triple.go | 1 + comid/cryptokey.go | 3 + comid/example_test.go | 2 +- comid/keytriple.go | 114 +++++++++-- comid/keytriple_test.go | 287 ++++++++++++++++++++++++++-- comid/triples.go | 2 +- comid/triples_test.go | 6 +- corim/unsignedcorim_test.go | 4 +- 8 files changed, 380 insertions(+), 39 deletions(-) diff --git a/comid/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go index 813f78b8..7c3fe283 100644 --- a/comid/cond_endorse_series_triple.go +++ b/comid/cond_endorse_series_triple.go @@ -54,6 +54,7 @@ func (o *CondEndorseSeriesCondition) MarshalCBOR() ([]byte, error) { return em.Marshal(toMarshal) } +// nolint:dupl func (o *CondEndorseSeriesCondition) UnmarshalCBOR(data []byte) error { var raw []cbor.RawMessage if err := dm.Unmarshal(data, &raw); err != nil { diff --git a/comid/cryptokey.go b/comid/cryptokey.go index a7850211..cde86c94 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -91,6 +91,9 @@ func (o CryptoKey) String() string { // Valid returns an error if validation of the CryptoKey fails, or nil if it // succeeds. func (o CryptoKey) Valid() error { + if o.Value == nil { + return errors.New("CryptoKey not set") + } return o.Value.Valid() } diff --git a/comid/example_test.go b/comid/example_test.go index a842a472..7b5e8961 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/keytriple.go b/comid/keytriple.go index 9f9a03fb..b4285dd0 100644 --- a/comid/keytriple.go +++ b/comid/keytriple.go @@ -1,28 +1,113 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid -import "fmt" +import ( + "errors" + "fmt" -// KeyTriple stores a cryptographic key triple record (identity-triple-record -// or attest-key-triple-record) with CBOR and JSON serializations. Note that -// the CBOR serialization packs the structure into an array. Instead, when -// serializing to JSON, the structure is converted into an object. + "github.com/fxamacker/cbor/v2" +) + +// KeyTripleCondition is the Conditions part of IdentityTriple, together +// with the Environment field, it is used to match an IdentityTriple to a +// Target Environment. +type KeyTripleCondition struct { + Mkey *Mkey `cbor:"0,keyasint,omitempty" json:"mkey,omitempty"` + AuthorizedBy *CryptoKeys `cbor:"1,keyasint,omitempty" json:"authorized-by,omitempty"` +} + +func (o *KeyTripleCondition) Valid() error { + if o.Mkey == nil && o.AuthorizedBy == nil { + return errors.New("condition must not be empty") + } + + if o.Mkey != nil { + if err := o.Mkey.Valid(); err != nil { + return fmt.Errorf("mkey: %w", err) + } + } + + if o.AuthorizedBy != nil { + if err := o.AuthorizedBy.Valid(); err != nil { + return fmt.Errorf("authorized-by: %w", err) + } + } + + return nil +} + +// KeyTriple endorses that contained keys were securely provisioned to the +// named Target Environment. +// +// attest-key-triple-record = [ +// environment: environment-map +// verification-keys: [ + $crypto-key-type-choice ] +// ? conditions: non-empty<{ +// ? &(mkey: 0) => $measured-element-type-choice, +// ? &(authorized-by: 1) => [ + $crypto-key-type-choice ] +// }> +// ] type KeyTriple struct { - _ struct{} `cbor:",toarray"` - Environment Environment `json:"environment"` - VerifKeys CryptoKeys `json:"verification-keys"` + Environment Environment `json:"environment"` + VerifKeys CryptoKeys `json:"verification-keys"` + Conditions *KeyTripleCondition `json:"conditions,omitempty"` } -func (o KeyTriple) Valid() error { +func (o *KeyTriple) Valid() error { if err := o.Environment.Valid(); err != nil { - return fmt.Errorf("environment validation failed: %w", err) + return fmt.Errorf("environment: %w", err) } if err := o.VerifKeys.Valid(); err != nil { - return fmt.Errorf("verification keys validation failed: %w", err) + return fmt.Errorf("verification-keys: %w", err) + } + + if o.Conditions != nil { + if err := o.Conditions.Valid(); err != nil { + return fmt.Errorf("conditions: %w", err) + } + } + + return nil +} + +func (o *KeyTriple) MarshalCBOR() ([]byte, error) { + toMarshal := []any{o.Environment, o.VerifKeys} + if o.Conditions != nil { + toMarshal = append(toMarshal, o.Conditions) } + + return em.Marshal(toMarshal) +} + +// nolint:dupl +func (o *KeyTriple) UnmarshalCBOR(data []byte) error { + var raw []cbor.RawMessage + if err := dm.Unmarshal(data, &raw); err != nil { + return err + } + + numElts := len(raw) + if numElts < 2 || numElts > 3 { + return fmt.Errorf("expected array between 2 and 3 elements; found %d", numElts) + } + + if err := dm.Unmarshal(raw[0], &o.Environment); err != nil { + return fmt.Errorf("environment: %w", err) + } + + if err := dm.Unmarshal(raw[1], &o.VerifKeys); err != nil { + return fmt.Errorf("verification-keys: %w", err) + } + + if numElts == 3 { + if err := dm.Unmarshal(raw[2], &o.Conditions); err != nil { + return fmt.Errorf("conditions: %w", err) + } + } + return nil } @@ -31,3 +116,8 @@ type KeyTriples []KeyTriple func NewKeyTriples() *KeyTriples { return &KeyTriples{} } + +func (o *KeyTriples) Add(it *KeyTriple) *KeyTriples { + *o = append(*o, *it) + return o +} diff --git a/comid/keytriple_test.go b/comid/keytriple_test.go index 334ad4fe..f6dd99c6 100644 --- a/comid/keytriple_test.go +++ b/comid/keytriple_test.go @@ -1,41 +1,288 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" ) -func TestVerificationKeys_Valid_empty(t *testing.T) { - invalidKey := CryptoKey{TaggedPKIXBase64Key("")} +func TestKeyTriple_Valid(t *testing.T) { + testCases := []struct { + title string + triple *KeyTriple + err string + }{ + { + title: "ok minimal", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + VerifKeys: *NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x04, 0x05, 0x06}, + )), + }, + }, + { + title: "ok full", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + VerifKeys: *NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x04, 0x05, 0x06}, + )), + Conditions: &KeyTripleCondition{ + Mkey: MustNewMkey("foo", "string"), + AuthorizedBy: NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x07, 0x08, 0x09}, + )), + }, + }, + }, + { + title: "bad no environment", + triple: &KeyTriple{}, + err: "environment must not be empty", + }, + { + title: "bad no key list", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + }, + err: "verification-keys: no keys to validate", + }, + { + title: "bad empty condition", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + VerifKeys: *NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x04, 0x05, 0x06}, + )), + Conditions: &KeyTripleCondition{}, + }, + err: "condition must not be empty", + }, + { + title: "bad invalid mkey", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + VerifKeys: *NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x04, 0x05, 0x06}, + )), + Conditions: &KeyTripleCondition{ + Mkey: &Mkey{}, + AuthorizedBy: NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x07, 0x08, 0x09}, + )), + }, + }, + err: "Mkey value not set", + }, + { + title: "bad invalid authorized-by", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + VerifKeys: *NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x04, 0x05, 0x06}, + )), + Conditions: &KeyTripleCondition{ + Mkey: MustNewMkey("foo", "string"), + AuthorizedBy: NewCryptoKeys().Add(&CryptoKey{}), + }, + }, + err: "invalid key at index 0: CryptoKey not set", + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + err := tc.triple.Valid() + if tc.err == "" { + assert.NoError(t, err) + } else { + assert.ErrorContains(t, err, tc.err) + } + }) + } +} + +func TestKeyTriple_round_trip(t *testing.T) { + testCases := []struct { + title string + triple *KeyTriple + expectedCBOR []byte + expectedJSON string + }{ + { + title: "minimal", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + VerifKeys: *NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x04, 0x05, 0x06}, + )), + }, + expectedCBOR: []byte{ + 0x82, // array(2) + 0xa1, // . [0]map(1) [environment] + 0x01, // . . key: 1 [instance] + 0xd9, 0x02, 0x30, // . . value: tag(560) [bytes] + 0x43, // . . . bstr(3) + 0x01, 0x02, 0x03, + 0x81, // . [1]array(1) [crypto-keys] + 0xd9, 0x02, 0x30, // . . value: tag(560) [bytes] + 0x43, // . . . bstr(3) + 0x04, 0x05, 0x06, + }, + expectedJSON: ` + { + "environment": { + "instance": {"type": "bytes", "value": "AQID"} + }, + "verification-keys": [ + {"type": "bytes", "value": "BAUG"} + ] + } + `, + }, + { + title: "full", + triple: &KeyTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + VerifKeys: *NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x04, 0x05, 0x06}, + )), + Conditions: &KeyTripleCondition{ + Mkey: MustNewMkey("foo", "string"), + AuthorizedBy: NewCryptoKeys().Add(MustNewCryptoKeyTaggedBytes( + []byte{0x07, 0x08, 0x09}, + )), + }, + }, + expectedCBOR: []byte{ + 0x83, // array(3) + 0xa1, // . [0]map(1) [environment] + 0x01, // . . key: 1 [instance] + 0xd9, 0x02, 0x30, // . . value: tag(560) [bytes] + 0x43, // . . . bstr(3) + 0x01, 0x02, 0x03, + 0x81, // . [1]array(1) [crypto-keys] + 0xd9, 0x02, 0x30, // . . value: tag(560) [bytes] + 0x43, // . . . bstr(3) + 0x04, 0x05, 0x06, + 0xa2, // . [2]map(2) [identy-triple-condition] + 0x00, // . . key: 0 [mkey] + 0x63, // . . value: tstr(3) + 0x66, 0x6f, 0x6f, // . . . "foo" + 0x01, // . . key: 1 [authorized-by] + 0x81, // . . value: array(1) [crypto-keys] + 0xd9, 0x02, 0x30, // . . . [0]tag(560) [bytes] + 0x43, // . . . . bstr(3) + 0x07, 0x08, 0x09, + }, + expectedJSON: ` + { + "environment": { + "instance": {"type": "bytes", "value": "AQID"} + }, + "verification-keys": [ + {"type": "bytes", "value": "BAUG"} + ], + "conditions": { + "mkey": {"type": "string", "value": "foo"}, + "authorized-by": [ + {"type": "bytes", "value": "BwgJ"} + ] + } + } + `, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + bytes, err := em.Marshal(&tc.triple) + assert.NoError(t, err) + assert.Equal(t, tc.expectedCBOR, bytes) + + var decoded KeyTriple + err = dm.Unmarshal(bytes, &decoded) + assert.NoError(t, err) + assert.EqualValues(t, tc.triple.Environment, decoded.Environment) + + bytes, err = json.Marshal(&tc.triple) + assert.NoError(t, err) + assert.JSONEq(t, tc.expectedJSON, string(bytes)) + + err = json.Unmarshal(bytes, &decoded) + assert.NoError(t, err) + assert.EqualValues(t, tc.triple.Environment, decoded.Environment) + }) + } +} - tvs := []struct { - env Environment - verifkey CryptoKeys - testerr string +func TestKeyTriple_UnmarshalCBOR_bad(t *testing.T) { + testCases := []struct { + title string + data []byte + err string }{ { - env: Environment{}, - verifkey: CryptoKeys{}, - testerr: "environment validation failed: environment must not be empty", + title: "invalid CBOR", + data: MustHexDecode(t, "81"), + err: "unexpected EOF", + }, + { + title: "wrong len", + data: MustHexDecode(t, "8101"), + err: "expected array between 2 and 3 elements", + }, + { + title: "bad environment", + data: MustHexDecode(t, "820102"), + err: "environment: cbor: cannot unmarshal positive integer", }, { - env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, - verifkey: CryptoKeys{}, - testerr: "verification keys validation failed: no keys to validate", + title: "bad keys", + data: MustHexDecode(t, "82a101d902304301020301"), + err: "verification-keys: cbor: cannot unmarshal positive integer", }, { - env: Environment{Instance: MustNewUEIDInstance(TestUEID)}, - verifkey: CryptoKeys{&invalidKey}, - testerr: "verification keys validation failed: invalid key at index 0: key value not set", + title: "bad condition", + data: MustHexDecode(t, "83a101d902304301020381d902304304050601"), + err: "conditions: cbor: cannot unmarshal positive integer", }, } - for _, tv := range tvs { - av := KeyTriple{Environment: tv.env, VerifKeys: tv.verifkey} - err := av.Valid() - assert.EqualError(t, err, tv.testerr) + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + var triple KeyTriple + err := triple.UnmarshalCBOR(tc.data) + assert.ErrorContains(t, err, tc.err) + }) } } + +func TestKeyTriples_Add(t *testing.T) { + triples := NewKeyTriples() + assert.Len(t, *triples, 0) + + triples.Add(&KeyTriple{}) + assert.Len(t, *triples, 1) +} diff --git a/comid/triples.go b/comid/triples.go index 89197fd0..ccda6f45 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid diff --git a/comid/triples_test.go b/comid/triples_test.go index 24d68b69..5fd72de5 100644 --- a/comid/triples_test.go +++ b/comid/triples_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -72,12 +72,12 @@ func TestTriples_Valid(t *testing.T) { triples.EndorsedValues = nil triples.AttestVerifKeys = &KeyTriples{{}} err = triples.Valid() - assert.EqualError(t, err, "attestation verification key at index 0: environment validation failed: environment must not be empty") + assert.EqualError(t, err, "attestation verification key at index 0: environment: environment must not be empty") triples.AttestVerifKeys = nil triples.DevIdentityKeys = &KeyTriples{{}} err = triples.Valid() - assert.EqualError(t, err, "device identity key at index 0: environment validation failed: environment must not be empty") + assert.EqualError(t, err, "device identity key at index 0: environment: environment must not be empty") triples.DevIdentityKeys = nil triples.CondEndorseSeries = &CondEndorseSeriesTriples{} diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 15e9d25b..2dbb0d5d 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -503,9 +503,9 @@ func TestComid_iterators(t *testing.T) { ) assert.NoError(t, errFunc()) - keySeq, errFunc = c.IterDevIdentityKeys() + identSeq, errFunc := c.IterDevIdentityKeys() assert.Equal(t, - slices.Collect(keySeq)[0].VerifKeys[0].String(), + slices.Collect(identSeq)[0].VerifKeys[0].String(), (*cm.Triples.DevIdentityKeys)[0].VerifKeys[0].String(), ) assert.NoError(t, errFunc()) From 881cc697212e5f42634c432e6116b6c70a1054f7 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 15 May 2026 16:00:38 +0100 Subject: [PATCH 12/18] feat(comid): add CoswidTriples Add CoswidTriples to the Triples struct. A CoSWID triple relates reference measurements contained in one or more CoSWIDs to a Target Environment. Signed-off-by: Sergei Trofimov --- comid/coswidtriple.go | 84 +++++++++++++++++++++++++++++++++++ comid/coswidtriple_test.go | 91 ++++++++++++++++++++++++++++++++++++++ comid/triples.go | 28 ++++++++++++ 3 files changed, 203 insertions(+) create mode 100644 comid/coswidtriple.go create mode 100644 comid/coswidtriple_test.go diff --git a/comid/coswidtriple.go b/comid/coswidtriple.go new file mode 100644 index 00000000..661488ca --- /dev/null +++ b/comid/coswidtriple.go @@ -0,0 +1,84 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "errors" + "fmt" + + "github.com/veraison/swid" +) + +type CoswidTagIDs []swid.TagID + +func (o CoswidTagIDs) Valid() error { + if len(o) == 0 { + return errors.New("must not be empty") + } + + for i, tag := range o { + if err := tag.Valid(); err != nil { + return fmt.Errorf("tag-id[%d]: %w", i, err) + } + } + + return nil +} + +func (o *CoswidTagIDs) Add(v *swid.TagID) *CoswidTagIDs { + *o = append(*o, *v) + return o +} + +// CoswidTriple relates reference measurements contained in one or more CoSWIDs +// to a Target Environment. +// +// ;# import rfc9393 as coswid +// +// coswid-triple-record = [ +// environment-map +// [ + coswid.tag-id ] +// ] +type CoswidTriple struct { + _ struct{} `cbor:",toarray"` + Environment Environment `json:"environment"` + TagIDs CoswidTagIDs `json:"tag-ids"` +} + +func (o CoswidTriple) Valid() error { + if err := o.Environment.Valid(); err != nil { + return fmt.Errorf("environment: %w", err) + } + + if err := o.TagIDs.Valid(); err != nil { + return fmt.Errorf("tag-ids: %w", err) + } + + return nil +} + +type CoswidTriples []CoswidTriple + +func NewCoswidTriples() *CoswidTriples { + return &CoswidTriples{} +} + +func (o *CoswidTriples) Add(triple *CoswidTriple) *CoswidTriples { + *o = append(*o, *triple) + return o +} + +func (o CoswidTriples) IsEmpty() bool { + return len(o) == 0 +} + +func (o CoswidTriples) Valid() error { + for i, triple := range o { + if err := triple.Valid(); err != nil { + return fmt.Errorf("triple[%d]: %w", i, err) + } + } + + return nil +} diff --git a/comid/coswidtriple_test.go b/comid/coswidtriple_test.go new file mode 100644 index 00000000..64dd89dd --- /dev/null +++ b/comid/coswidtriple_test.go @@ -0,0 +1,91 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/veraison/swid" +) + +func TestCoswidTriple_Valid(t *testing.T) { + testCasses := []struct { + title string + triple *CoswidTriple + err string + }{ + { + title: "ok", + triple: &CoswidTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + TagIDs: CoswidTagIDs{*swid.NewTagID("1.2.3.4")}, + }, + }, + { + title: "bad empty environment", + triple: &CoswidTriple{}, + err: "environment must not be empty", + }, + { + title: "bad empty tag IDs", + triple: &CoswidTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + }, + err: "tag-ids: must not be empty", + }, + { + title: "bad invalid tag", + triple: &CoswidTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + TagIDs: CoswidTagIDs{swid.TagID{}}, + }, + err: "tag-id[0]: tag-id value is nil", + }, + } + + for _, tc := range testCasses { + t.Run(tc.title, func(t *testing.T) { + err := tc.triple.Valid() + if tc.err == "" { + assert.NoError(t, err) + } else { + assert.ErrorContains(t, err, tc.err) + } + }) + } +} + +func TestCoswidTagIDs_Add(t *testing.T) { + tagIDs := CoswidTagIDs{} + tagIDs.Add(swid.NewTagID("1.2.3.4")) + assert.Len(t, tagIDs, 1) +} + +func TestCoswidTriples(t *testing.T) { + triples := NewCoswidTriples() + assert.True(t, triples.IsEmpty()) + + triples.Add(&CoswidTriple{}) + assert.False(t, triples.IsEmpty()) + + err := triples.Valid() + assert.ErrorContains(t, err, "triple[0]: environment: environment must not be empty") + + (*triples)[0] = CoswidTriple{ + Environment: Environment{ + Instance: MustNewBytesInstance([]byte{0x01, 0x02, 0x03}), + }, + TagIDs: CoswidTagIDs{*swid.NewTagID("1.2.3.4")}, + } + + err = triples.Valid() + assert.NoError(t, err) +} diff --git a/comid/triples.go b/comid/triples.go index ccda6f45..1ac5d8c2 100644 --- a/comid/triples.go +++ b/comid/triples.go @@ -18,6 +18,7 @@ type Triples struct { AttestVerifKeys *KeyTriples `cbor:"3,keyasint,omitempty" json:"attester-verification-keys,omitempty"` DomainDependencies *DomainDependencyTriples `cbor:"4,keyasint,omitempty" json:"dependency-triples,omitempty"` DomainMemberships *DomainMembershipTriples `cbor:"5,keyasint,omitempty" json:"membership-triples,omitempty"` + CoswidTriples *CoswidTriples `cbor:"6,keyasint,omitempty" json:"coswid-triples,omitempty"` CondEndorseSeries *CondEndorseSeriesTriples `cbor:"8,keyasint,omitempty" json:"conditional-endorsement-series,omitempty"` CondEndorsements *CondEndorseTriples `cbor:"10,keyasint,omitempty" json:"conditional-endorsements,omitempty"` Extensions @@ -141,6 +142,10 @@ func (o Triples) MarshalCBOR() ([]byte, error) { o.DomainMemberships = nil } + if o.CoswidTriples != nil && o.CoswidTriples.IsEmpty() { + o.CoswidTriples = nil + } + return encoding.SerializeStructToCBOR(em, o) } @@ -182,6 +187,10 @@ func (o Triples) MarshalJSON() ([]byte, error) { o.DomainMemberships = nil } + if o.CoswidTriples != nil && o.CoswidTriples.IsEmpty() { + o.CoswidTriples = nil + } + return encoding.SerializeStructToJSON(o) } @@ -259,6 +268,7 @@ func (o *Triples) Valid() error { (o.DevIdentityKeys == nil || len(*o.DevIdentityKeys) == 0) && (o.DomainDependencies == nil || o.DomainDependencies.IsEmpty()) && (o.DomainMemberships == nil || o.DomainMemberships.IsEmpty()) && + (o.CoswidTriples == nil || o.CoswidTriples.IsEmpty()) && (o.CondEndorseSeries == nil || o.CondEndorseSeries.IsEmpty()) && (o.CondEndorsements == nil || o.CondEndorsements.IsEmpty()) { return fmt.Errorf("triples struct must not be empty") @@ -304,6 +314,12 @@ func (o *Triples) Valid() error { } } + if o.CoswidTriples != nil { + if err := o.CoswidTriples.Valid(); err != nil { + return fmt.Errorf("coswid triples: %w", err) + } + } + if o.CondEndorseSeries != nil { if err := o.CondEndorseSeries.Valid(); err != nil { return fmt.Errorf("conditional series: %w", err) @@ -383,6 +399,18 @@ func (o *Triples) AddDomainMembership(val *DomainMembershipTriple) *Triples { return o } +func (o *Triples) AddCoswidTriple(val *CoswidTriple) *Triples { + if o != nil { + if o.CoswidTriples == nil { + o.CoswidTriples = NewCoswidTriples() + } + + o.CoswidTriples.Add(val) + } + + return o +} + // nolint:gocritic func (o *Triples) AddCondEndorseSeries(val *CondEndorseSeriesTriple) *Triples { if o != nil { From b2d7b47a5cef5d57d8bb166f3fbe17a56719b0f7 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 18 May 2026 15:36:25 +0100 Subject: [PATCH 13/18] feat(comid): add key types to instance type choice Rev. 8 of the spec added the existing CrypoKey types (sans chain types) to possible variants of the instance-id-type-choice. This updates Instance to support key types. Signed-off-by: Sergei Trofimov --- comid/cryptokey.go | 28 ++++++++ comid/cryptokey_test.go | 4 ++ comid/instance.go | 146 ++++++++++++++++++++++++++++++++++++++-- comid/instance_test.go | 71 ++++++++++++++++++- 4 files changed, 244 insertions(+), 5 deletions(-) diff --git a/comid/cryptokey.go b/comid/cryptokey.go index cde86c94..bbe5d383 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -173,6 +173,9 @@ type ICryptoKeyValue interface { // ICryptoKeyValue's underlying value. This returns an error if the // ICryptoKeyValue is one of the thumbprint types. PublicKey() (crypto.PublicKey, error) + + // Bytes returns the raw bytes of the key + Bytes() []byte } // TaggedPKIXBase64Key is a PEM-encoded SubjectPublicKeyInfo. See @@ -218,6 +221,10 @@ func (o TaggedPKIXBase64Key) Type() string { return PKIXBase64KeyType } +func (o TaggedPKIXBase64Key) Bytes() []byte { + return []byte(o) +} + func (o TaggedPKIXBase64Key) PublicKey() (crypto.PublicKey, error) { if string(o) == "" { return nil, errors.New("key value not set") @@ -290,6 +297,10 @@ func (o TaggedPKIXBase64Cert) Type() string { return PKIXBase64CertType } +func (o TaggedPKIXBase64Cert) Bytes() []byte { + return []byte(o) +} + func (o TaggedPKIXBase64Cert) PublicKey() (crypto.PublicKey, error) { cert, err := o.cert() if err != nil { @@ -380,6 +391,10 @@ func (o TaggedPKIXBase64CertPath) Type() string { return PKIXBase64CertPathType } +func (o TaggedPKIXBase64CertPath) Bytes() []byte { + return []byte(o) +} + func (o TaggedPKIXBase64CertPath) PublicKey() (crypto.PublicKey, error) { certs, err := o.certPath() if err != nil { @@ -504,6 +519,10 @@ func (o TaggedCOSEKey) Type() string { return COSEKeyType } +func (o TaggedCOSEKey) Bytes() []byte { + return []byte(o) +} + func (o TaggedCOSEKey) PublicKey() (crypto.PublicKey, error) { if len(o) == 0 { return nil, errors.New("empty COSE_Key value") @@ -622,6 +641,10 @@ func (o TaggedPKIXAsn1DerCert) Type() string { return PKIXBase64CertType } +func (o TaggedPKIXAsn1DerCert) Bytes() []byte { + return []byte(o) +} + func (o TaggedPKIXAsn1DerCert) PublicKey() (crypto.PublicKey, error) { cert, err := o.cert() if err != nil { @@ -664,6 +687,11 @@ func (o digest) PublicKey() (crypto.PublicKey, error) { return nil, errors.New("cannot get PublicKey from a digest") } +func (o digest) Bytes() []byte { + ret, _ := em.Marshal(o) + return ret +} + // ThumbprintTypeTaggedThumbprint represents a digest of a raw public key. The // digest value may be used to find the public key if contained in a lookup // table. diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index f73eaeb3..5a5d7ac1 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -453,6 +453,10 @@ func (o testCryptoKey) String() string { return "test" } +func (o testCryptoKey) Bytes() []byte { + return []byte("test") +} + func (o testCryptoKey) Valid() error { return nil } diff --git a/comid/instance.go b/comid/instance.go index 5adc46cd..5c1fa894 100644 --- a/comid/instance.go +++ b/comid/instance.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -249,6 +249,138 @@ func MustNewUUIDInstance(val any) *Instance { return ret } +// NewPKIXBase64KeyInstance instantiates a new instance with the supplied +// PEM-encoded PKIX key identity. +func NewPKIXBase64KeyInstance(val any) (*Instance, error) { + ck, err := NewPKIXBase64Key(val) + if err != nil { + return nil, err + } + + return &Instance{ck.Value}, nil +} + +// MustNewPKIXBase64KeyInstance is like NewPKIXBase64KeyInstance but panics on +// error. +func MustNewPKIXBase64KeyInstance(val any) *Instance { + ret, err := NewPKIXBase64KeyInstance(val) + if err != nil { + panic(err) + } + + return ret +} + +// NewPKIXBase64CertInstance instantiates a new instance with the supplied +// PEM-encoded X.509 certificate identity. +func NewPKIXBase64CertInstance(val any) (*Instance, error) { + ck, err := NewPKIXBase64Cert(val) + if err != nil { + return nil, err + } + + return &Instance{ck.Value}, nil +} + +// MustNewPKIXBase64CertInstance is like NewPKIXBase64CertInstance but panics on +// error. +func MustNewPKIXBase64CertInstance(val any) *Instance { + ret, err := NewPKIXBase64CertInstance(val) + if err != nil { + panic(err) + } + + return ret +} + +// NewCOSEKeyInstance instantiates a new instance with the supplied +// COSE key identity. +func NewCOSEKeyInstance(val any) (*Instance, error) { + ck, err := NewCOSEKey(val) + if err != nil { + return nil, err + } + + return &Instance{ck.Value}, nil +} + +// MustNewCOSEKeyInstance is like NewCOSEKeyInstance but panics on +// error. +func MustNewCOSEKeyInstance(val any) *Instance { + ret, err := NewCOSEKeyInstance(val) + if err != nil { + panic(err) + } + + return ret +} + +// NewThumbprintInstance instantiates a new instance with the supplied +// key thumbprint identity. +func NewThumbprintInstance(val any) (*Instance, error) { + ck, err := NewThumbprint(val) + if err != nil { + return nil, err + } + + return &Instance{ck.Value}, nil +} + +// MustNewThumbprintInstance is like NewThumbprintInstance but panics on +// error. +func MustNewThumbprintInstance(val any) *Instance { + ret, err := NewThumbprintInstance(val) + if err != nil { + panic(err) + } + + return ret +} + +// NewCertThumbprintInstance instantiates a new instance with the supplied +// certificate thumbprint identity. +func NewCertThumbprintInstance(val any) (*Instance, error) { + ck, err := NewCertThumbprint(val) + if err != nil { + return nil, err + } + + return &Instance{ck.Value}, nil +} + +// MustNewCertThumbprintInstance is like NewCertThumbprintInstance but panics +// on error. +func MustNewCertThumbprintInstance(val any) *Instance { + ret, err := NewCertThumbprintInstance(val) + if err != nil { + panic(err) + } + + return ret +} + +// NewPKIXAsn1DerCertInstance instantiates a new instance with the supplied +// ASN.1 DER-encoded X.509 certificate identity. +func NewPKIXAsn1DerCertInstance(val any) (*Instance, error) { + ck, err := NewPKIXAsn1DerCert(val) + if err != nil { + return nil, err + } + + return &Instance{ck.Value}, nil +} + +// MustNewPKIXAsn1DerCertInstance is like NewPKIXAsn1DerCertInstance but panics on +// error. +func MustNewPKIXAsn1DerCertInstance(val any) *Instance { + ret, err := NewPKIXAsn1DerCertInstance(val) + if err != nil { + panic(err) + } + + return ret +} + // IInstanceFactory defines the signature for the factory functions that may be // registered using RegisterInstanceType to provide a new implementation of the // corresponding type choice. The factory function should create a new *Instance @@ -260,9 +392,15 @@ func MustNewUUIDInstance(val any) *Instance { type IInstanceFactory func(any) (*Instance, error) var instanceValueRegister = map[string]IInstanceFactory{ - UEIDType: NewUEIDInstance, - UUIDType: NewUUIDInstance, - BytesType: NewBytesInstance, + UEIDType: NewUEIDInstance, + UUIDType: NewUUIDInstance, + BytesType: NewBytesInstance, + PKIXBase64KeyType: NewPKIXBase64KeyInstance, + PKIXBase64CertType: NewPKIXBase64CertInstance, + COSEKeyType: NewCOSEKeyInstance, + ThumbprintType: NewThumbprintInstance, + CertThumbprintType: NewCertThumbprintInstance, + PKIXAsn1DerCertType: NewPKIXAsn1DerCertInstance, } // RegisterInstanceType registers a new IInstanceValue implementation (created diff --git a/comid/instance_test.go b/comid/instance_test.go index cc0497c2..4a07543d 100644 --- a/comid/instance_test.go +++ b/comid/instance_test.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -222,3 +222,72 @@ func TestInstance_UnmarshalJSON_Bytes_NOK(t *testing.T) { }) } } + +func TestNewPKIXBase64KeyInstance(t *testing.T) { + _, err := NewPKIXBase64KeyInstance(TestECPubKey) + assert.NoError(t, err) + + MustNewPKIXBase64KeyInstance(TestECPubKey) + + _, err = NewPKIXBase64KeyInstance("foo") + assert.ErrorContains(t, err, "could not decode PEM block") + + assert.Panics(t, func() { MustNewPKIXBase64KeyInstance("foo") }) +} + +func TestNewPKIXBase64CertInstance(t *testing.T) { + _, err := NewPKIXBase64CertInstance(TestCert) + assert.NoError(t, err) + + MustNewPKIXBase64CertInstance(TestCert) + + _, err = NewPKIXBase64CertInstance("foo") + assert.ErrorContains(t, err, "could not decode PEM block") + + assert.Panics(t, func() { MustNewPKIXBase64CertInstance("foo") }) +} + +func TestNewCOSEKeyInstance(t *testing.T) { + _, err := NewCOSEKeyInstance(TestCOSEKey) + assert.NoError(t, err) + + MustNewCOSEKeyInstance(TestCOSEKey) + + _, err = NewCOSEKeyInstance("foo") + assert.ErrorContains(t, err, "base64 decode error") + + assert.Panics(t, func() { MustNewCOSEKeyInstance("foo") }) +} + +func TestNewThumbprintInstance(t *testing.T) { + testCases := []struct { + title string + newFunc func(any) (*Instance, error) + mustNewFunc func(any) *Instance + }{ + { + title: "thumbprint", + newFunc: NewThumbprintInstance, + mustNewFunc: MustNewThumbprintInstance, + }, + { + title: "cert thumbprint", + newFunc: NewCertThumbprintInstance, + mustNewFunc: MustNewCertThumbprintInstance, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + _, err := tc.newFunc(TestThumbprint) + assert.NoError(t, err) + + tc.mustNewFunc(TestThumbprint) + + _, err = tc.newFunc(7) + assert.ErrorContains(t, err, "must be a Digest or a string") + + assert.Panics(t, func() { tc.mustNewFunc("foo") }) + }) + } +} From 6cf5b1e8370d00504b393bdd3875fee4d3aafe13 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 18 May 2026 15:51:37 +0100 Subject: [PATCH 14/18] fix(comid): marshaling tags for TaggedRawIntRange Add tags to TaggedRawIntRange struct to ensure that it is marshaled as array in CBOR and uses lower-case fields in JSON. Signed-off-by: Sergei Trofimov --- comid/rawint.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/comid/rawint.go b/comid/rawint.go index c4739047..0b396fd6 100644 --- a/comid/rawint.go +++ b/comid/rawint.go @@ -239,8 +239,9 @@ const TaggedRawIntRangeType = "rawIntRange" // to be negative infinity. If the maximum is nil, it's assumed to be // positive infinity. type TaggedRawIntRange struct { - Min *int64 - Max *int64 + _ struct{} `cbor:",toarray"` + Min *int64 `json:"min,omitempty"` + Max *int64 `json:"max,omitempty"` } // NewRawIntRange creates a TaggedRawIntRange with the input value From 3610967aae20db60787ca9a868eca71ddd45ec5d Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 18 May 2026 11:20:02 +0100 Subject: [PATCH 15/18] feat(comid): implement ITypeChoiceValue for TaggedURI Move TaggedURI out of entity.go into its own file. Implement ITypeChoiceValue interface, allowing TaggedURI to be used as a type choice variant. Signed-off-by: Sergei Trofimov --- comid/entity.go | 8 +----- comid/uri.go | 68 +++++++++++++++++++++++++++++++++++++++++++++ comid/uri_test.go | 70 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 139 insertions(+), 7 deletions(-) create mode 100644 comid/uri.go create mode 100644 comid/uri_test.go diff --git a/comid/entity.go b/comid/entity.go index 170f706a..6175623a 100644 --- a/comid/entity.go +++ b/comid/entity.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -332,9 +332,3 @@ func RegisterEntityNameType(tag uint64, factory IEntityNameFactory) error { return nil } - -type TaggedURI string - -func (o TaggedURI) Empty() bool { - return o == "" -} diff --git a/comid/uri.go b/comid/uri.go new file mode 100644 index 00000000..9b6b0e75 --- /dev/null +++ b/comid/uri.go @@ -0,0 +1,68 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "errors" + "fmt" + "net/url" +) + +const URIType = "uri" + +type TaggedURI string + +func NewTaggedURI(val any) (*TaggedURI, error) { + if val == nil { + ret := TaggedURI("") + return &ret, nil + } + + switch t := val.(type) { + case string: + ret := TaggedURI(t) + if err := ret.Valid(); err != nil { + return nil, err + } + return &ret, nil + case *string: + ret := TaggedURI(*t) + if err := ret.Valid(); err != nil { + return nil, err + } + return &ret, nil + case url.URL: + ret := TaggedURI(t.String()) + return &ret, nil + case *url.URL: + ret := TaggedURI(t.String()) + return &ret, nil + default: + return nil, fmt.Errorf("unexpected input type for URI: %v(%T)", t, t) + } +} + +func (o TaggedURI) Empty() bool { + return o == "" +} + +func (o TaggedURI) Type() string { + return "uri" +} + +func (o TaggedURI) String() string { + return string(o) +} + +func (o TaggedURI) Valid() error { + if o.Empty() { + return errors.New("empty URI") + } + + if _, err := url.Parse(string(o)); err != nil { + return err + } + + return nil +} diff --git a/comid/uri_test.go b/comid/uri_test.go new file mode 100644 index 00000000..b4d74fe3 --- /dev/null +++ b/comid/uri_test.go @@ -0,0 +1,70 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "net/url" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestNewTaggedURI(t *testing.T) { + textValue := "http://example.com" + urlValue := url.URL{Scheme: "http", Host: "example.com"} + badTextValue := "http://[::" + + testCases := []struct { + title string + value any + err string + }{ + { + title: "ok nil", + value: nil, + }, + { + title: "ok string", + value: textValue, + }, + { + title: "ok *string", + value: &textValue, + }, + { + title: "ok url.URL", + value: urlValue, + }, + { + title: "ok *url.URL", + value: &urlValue, + }, + { + title: "bad invalid string", + value: badTextValue, + err: "missing ']' in host", + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + _, err := NewTaggedURI(tc.value) + if tc.err == "" { + assert.NoError(t, err) + } else { + assert.ErrorContains(t, err, tc.err) + } + }) + } +} + +func TestTaggedURI_ITypeChoiceValue_methods(t *testing.T) { + u := TaggedURI("") + err := u.Valid() + assert.ErrorContains(t, err, "empty URI") + + u = TaggedURI("http://example.com") + assert.Equal(t, "http://example.com", u.String()) + assert.Equal(t, "uri", u.Type()) +} From 1528453f54bd3493784599ea602269bd7361c711 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 18 May 2026 11:21:52 +0100 Subject: [PATCH 16/18] fix(corim)!: align Profile with CoRIM spec Align definition of Profile with the CoRIM spec, which defines it as a uri or tagged-oid-type (or some extension variant). Up to this point, the implementation re-used EAT Profile. That is defined similarly, but both of its variants are untagged (it also does not allow extension). Define a new Profile type implementing the existing type choice pattern with TaggedOID and TaggedURI variants, and allowing registering of additional variants. BREAKING CHANGE: UnsignedCorim.Profile type changed form *eat.Profile to *Profile; both CBOR and JSON encodings now encode profile as a tagged value rather than string. Signed-off-by: Sergei Trofimov --- coev/profiles.go | 39 ++-- coev/tdx/example_pce_test.go | 16 +- coev/tdx/example_qe_test.go | 16 +- coev/tdx/example_seam_test.go | 16 +- coev/tdx/tdx_profile.go | 10 +- corim/cbor.go | 3 +- corim/example_profile_test.go | 15 +- corim/profile.go | 188 ++++++++++++++++++ corim/profile_test.go | 138 +++++++++++++ corim/profiles.go | 46 ++--- corim/profiles_test.go | 33 ++- corim/testcases/corim-ext.json | 5 +- .../signed-corim-with-extensions.cbor | Bin 590 -> 592 bytes corim/testcases/signed-example-corim.cbor | Bin 593 -> 595 bytes corim/testcases/signed-good-corim.cbor | Bin 516 -> 516 bytes .../testcases/src/corim-with-extensions.yaml | 4 +- corim/testcases/src/example-corim.yaml | 4 +- .../unsigned-corim-with-extensions.cbor | Bin 420 -> 422 bytes corim/testcases/unsigned-example-corim.cbor | Bin 423 -> 425 bytes corim/testcases/unsigned-good-corim.cbor | Bin 346 -> 346 bytes corim/unsignedcorim.go | 26 +-- corim/unsignedcorim_test.go | 5 +- profiles/cca/corim_test.go | 5 +- profiles/cca/platform.go | 6 +- profiles/cca/realm.go | 6 +- .../cca-platform-invalid-digests.cbor | Bin 262 -> 264 bytes .../cca-platform-invalid-impl-id.cbor | Bin 261 -> 263 bytes .../cca/testcases/cca-platform-valid.cbor | Bin 639 -> 641 bytes .../cca-realm-invalid-missing-rim.cbor | Bin 203 -> 205 bytes .../testcases/cca-realm-invalid-rim-size.cbor | Bin 182 -> 184 bytes profiles/cca/testcases/cca-realm-valid.cbor | Bin 454 -> 456 bytes profiles/cca/testcases/no-profile.cbor | Bin 212 -> 212 bytes .../src/cca-platform-invalid-digests.yaml | 4 +- .../src/cca-platform-invalid-impl-id.yaml | 4 +- .../cca/testcases/src/cca-platform-valid.yaml | 4 +- .../src/cca-realm-invalid-missing-rim.yaml | 4 +- .../src/cca-realm-invalid-rim-size.yaml | 4 +- .../cca/testcases/src/cca-realm-valid.yaml | 4 +- profiles/psa/corim_test.go | 5 +- profiles/psa/psa.go | 6 +- profiles/psa/testcases/no-profile.cbor | Bin 282 -> 282 bytes .../psa/testcases/psa-invalid-attest-key.cbor | Bin 611 -> 613 bytes .../psa/testcases/psa-invalid-impl-id.cbor | Bin 320 -> 322 bytes profiles/psa/testcases/psa-valid.cbor | Bin 595 -> 597 bytes .../testcases/src/psa-invalid-attest-key.yaml | 4 +- .../testcases/src/psa-invalid-impl-id.yaml | 4 +- profiles/psa/testcases/src/psa-valid.yaml | 4 +- profiles/tdx/example_pce_refval_test.go | 23 +-- profiles/tdx/example_qe_refval_test.go | 13 +- profiles/tdx/example_seam_refval_test.go | 23 +-- profiles/tdx/mval_extensions.go | 8 +- 51 files changed, 466 insertions(+), 229 deletions(-) create mode 100644 corim/profile.go create mode 100644 corim/profile_test.go diff --git a/coev/profiles.go b/coev/profiles.go index bf29fdc5..122c6301 100644 --- a/coev/profiles.go +++ b/coev/profiles.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package coev @@ -7,15 +7,15 @@ import ( "fmt" "reflect" + "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" ) // ProfileManifest associates an EAT profile ID with a set of extensions. It allows // obtaining new Concise Evidence structure that had associated extensions // registered. type ProfileManifest struct { - ID *eat.Profile + ID *corim.Profile MapExtensions extensions.Map } @@ -74,12 +74,13 @@ func (o *ProfileManifest) registerExtensions(e iextensible, points []extensions. // RegisterProfile registers a set of extensions with the specified profile. If // the profile has already been registered, or if the extensions are invalid, // an error is returned. -func RegisterProfile(id *eat.Profile, exts extensions.Map) error { - strID, err := id.Get() - if err != nil { +func RegisterProfile(id *corim.Profile, exts extensions.Map) error { + if err := id.Valid(); err != nil { return err } + strID := id.String() + if _, ok := profilesRegister[strID]; ok { return fmt.Errorf("profile with id %q already registered", strID) } @@ -102,32 +103,24 @@ func RegisterProfile(id *eat.Profile, exts extensions.Map) error { // GetProfileManifest returns the ProfileManifest associated with the specified ID, or an empty // profileManifest if no ProfileManifest has been registered for the ID. The second return // value indicates whether a profileManifest for the ID has been found. -func GetProfileManifest(id *eat.Profile) (*ProfileManifest, bool) { - if id == nil { +func GetProfileManifest(id *corim.Profile) (*ProfileManifest, bool) { + if id.IsNil() { return nil, false } - strID, err := id.Get() - if err != nil { - return nil, false - } - - prof, ok := profilesRegister[strID] + prof, ok := profilesRegister[id.String()] return &prof, ok } // UnregisterProfile ensures there are no extensions registered for the // specified profile ID. Returns true if extensions were previously registered // and have been removed, and false otherwise. -func UnregisterProfile(id *eat.Profile) bool { - if id == nil { +func UnregisterProfile(id *corim.Profile) bool { + if id.IsNil() { return false } - strID, err := id.Get() - if err != nil { - return false - } + strID := id.String() if _, ok := profilesRegister[strID]; ok { delete(profilesRegister, strID) @@ -140,7 +133,7 @@ func UnregisterProfile(id *eat.Profile) bool { // UnmarshalConciseEvidenceFromCBOR unmarshals a ConciseEvidence from provided CBOR data. If // there are extensions associated with the profile specified by the data, they // will be registered with the coev.ConciseEvidence before it is unmarshaled. -func UnmarshalConciseEvidenceFromCBOR(buf []byte, profileID *eat.Profile) (*ConciseEvidence, error) { +func UnmarshalConciseEvidenceFromCBOR(buf []byte, profileID *corim.Profile) (*ConciseEvidence, error) { var ret *ConciseEvidence profileManifest, ok := GetProfileManifest(profileID) @@ -160,10 +153,10 @@ func UnmarshalConciseEvidenceFromCBOR(buf []byte, profileID *eat.Profile) (*Conc // GetConciseEvidence returns a pointer to a new ConciseEvidence instance. If there // are extensions associated with the provided profileID, they will be // registered with the instance. -func GetConciseEvidence(profileID *eat.Profile) *ConciseEvidence { +func GetConciseEvidence(profileID *corim.Profile) *ConciseEvidence { var ret *ConciseEvidence - if profileID == nil { + if profileID.IsNil() { ret = NewConciseEvidence() } else { profileManifest, ok := GetProfileManifest(profileID) diff --git a/coev/tdx/example_pce_test.go b/coev/tdx/example_pce_test.go index 941af5ca..9da2aff4 100644 --- a/coev/tdx/example_pce_test.go +++ b/coev/tdx/example_pce_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -11,9 +11,9 @@ import ( "github.com/veraison/corim/coev" "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" "github.com/veraison/corim/profiles/tdx" - "github.com/veraison/eat" ) func Example_decode_PCE_Evidence_JSON() { @@ -113,10 +113,7 @@ func Example_encode_tdx_pce_evidence_without_profile() { } func Example_encode_tdx_pce_evidence_with_profile() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := coev.GetProfileManifest(profileID) if !found { @@ -136,7 +133,7 @@ func Example_encode_tdx_pce_evidence_with_profile() { valTriple.Measurements.Add(measurement) coEv.EvTriples.EvidenceTriples.Add(valTriple) - if err = tdx.SetTdxPceMvalExtensions(tdx.Evidence, &coEv.EvTriples.EvidenceTriples.Values[0].Measurements.Values[0].Val); err != nil { + if err := tdx.SetTdxPceMvalExtensions(tdx.Evidence, &coEv.EvTriples.EvidenceTriples.Values[0].Measurements.Values[0].Val); err != nil { panic(err) } @@ -170,10 +167,7 @@ var ( ) func Example_decode_PCE_Evidence_CBOR() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := coev.GetProfileManifest(profileID) if !found { fmt.Printf("Evidence Profile NOT FOUND") diff --git a/coev/tdx/example_qe_test.go b/coev/tdx/example_qe_test.go index de044356..bf9c0a99 100644 --- a/coev/tdx/example_qe_test.go +++ b/coev/tdx/example_qe_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -11,9 +11,9 @@ import ( "github.com/veraison/corim/coev" "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" "github.com/veraison/corim/profiles/tdx" - "github.com/veraison/eat" ) func Example_decode_QE_Evidence_JSON() { @@ -108,10 +108,7 @@ func Example_encode_tdx_qe_evidence_without_profile() { } func Example_encode_tdx_qe_evidence_with_profile() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := coev.GetProfileManifest(profileID) if !found { @@ -131,7 +128,7 @@ func Example_encode_tdx_qe_evidence_with_profile() { valTriple.Measurements.Add(measurement) coEv.EvTriples.EvidenceTriples.Add(valTriple) - if err = tdx.SetTdxQeMvalExtensions(tdx.Evidence, &coEv.EvTriples.EvidenceTriples.Values[0].Measurements.Values[0].Val); err != nil { + if err := tdx.SetTdxQeMvalExtensions(tdx.Evidence, &coEv.EvTriples.EvidenceTriples.Values[0].Measurements.Values[0].Val); err != nil { panic(err) } @@ -165,10 +162,7 @@ var ( ) func Example_decode_QE_Evidence_CBOR() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := coev.GetProfileManifest(profileID) if !found { fmt.Printf("Evidence Profile NOT FOUND") diff --git a/coev/tdx/example_seam_test.go b/coev/tdx/example_seam_test.go index 2a7a0982..643da822 100644 --- a/coev/tdx/example_seam_test.go +++ b/coev/tdx/example_seam_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -11,9 +11,9 @@ import ( "github.com/veraison/corim/coev" "github.com/veraison/corim/comid" + "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" "github.com/veraison/corim/profiles/tdx" - "github.com/veraison/eat" ) func Example_decode_Seam_Evidence_JSON() { @@ -110,10 +110,7 @@ var ( ) func Example_encode_tdx_seam_evidence_with_profile() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := coev.GetProfileManifest(profileID) if !found { @@ -132,7 +129,7 @@ func Example_encode_tdx_seam_evidence_with_profile() { valTriple.Measurements.Add(measurement) coEv.EvTriples.EvidenceTriples.Add(valTriple) - if err = tdx.SetTDXSeamMvalExtensions(tdx.Evidence, &coEv.EvTriples.EvidenceTriples.Values[0].Measurements.Values[0].Val); err != nil { + if err := tdx.SetTDXSeamMvalExtensions(tdx.Evidence, &coEv.EvTriples.EvidenceTriples.Values[0].Measurements.Values[0].Val); err != nil { panic(err) } @@ -161,10 +158,7 @@ func Example_encode_tdx_seam_evidence_with_profile() { } func Example_decode_CBOR() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := coev.GetProfileManifest(profileID) if !found { fmt.Printf("Evidence Profile NOT FOUND") diff --git a/coev/tdx/tdx_profile.go b/coev/tdx/tdx_profile.go index c991585e..925a4539 100644 --- a/coev/tdx/tdx_profile.go +++ b/coev/tdx/tdx_profile.go @@ -5,12 +5,12 @@ package tdx import ( "github.com/veraison/corim/coev" + "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" "github.com/veraison/corim/profiles/tdx" - "github.com/veraison/eat" ) -var ProfileID *eat.Profile +var ProfileID *corim.Profile // Registering the profile inside init() in the same file where it is defined // ensures that the profile will always be available, and you don't need to @@ -22,11 +22,7 @@ var ProfileID *eat.Profile // which is "joint-iso-itu-t.country.us.organization.intel.intel-comid.profile" func init() { - var err error - ProfileID, err = eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + ProfileID = corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") extMap := extensions.NewMap(). Add(coev.ExtEvidenceTriples, &tdx.MValExtensions{}) diff --git a/corim/cbor.go b/corim/cbor.go index e522c694..f7dedf49 100644 --- a/corim/cbor.go +++ b/corim/cbor.go @@ -22,7 +22,8 @@ var ( ComidTag uint64 = 506 corimTagsMap = map[uint64]interface{}{ - 32: comid.TaggedURI(""), + 32: comid.TaggedURI(""), + 111: comid.TaggedOID{}, } ) diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index 88bf7024..df06e7cd 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -12,7 +12,6 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" "github.com/veraison/swid" ) @@ -56,10 +55,7 @@ func (*ComidExtensions) ConstrainComid(c *comid.Comid) error { // which should not happen if it a registered PEN or a URL containing a domain // that you own. func init() { - profileID, err := eat.NewProfile("http://example.com/example-profile") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := MustNewURIProfile("http://example.com/example-profile") extMap := extensions.NewMap(). Add(ExtEntity, &EntityExtensions{}). @@ -128,10 +124,7 @@ func Example_profile_unmarshal() { // then unmarshal that into a CoRIM before marshaling it into CBOR (in which // case, extensions will work as with unmarshaling example above). func Example_profile_marshal() { - profileID, err := eat.NewProfile("http://example.com/example-profile") - if err != nil { - panic(err) - } + profileID := MustNewURIProfile("http://example.com/example-profile") profileManifest, ok := GetProfileManifest(profileID) if !ok { @@ -146,7 +139,7 @@ func Example_profile_marshal() { AddEntity("ACME Ltd.", &comid.TestRegID, comid.RoleCreator) address := "123 Fake Street" - err = myComid.Entities.Values[0].Set("Address", &address) + err := myComid.Entities.Values[0].Set("Address", &address) if err != nil { log.Fatalf("could not set entity Address: %v", err) } @@ -199,5 +192,5 @@ func Example_profile_marshal() { fmt.Printf("corim: %v", hex.EncodeToString(buf)) // output: - // corim: d901f5a30063666f6f0181d901fa58a5a40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026e526f616452756e6e657220322e3081a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 + // corim: d901f5a30063666f6f0181d901fa58a5a40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026e526f616452756e6e657220322e3081a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a10281820644abcdef0003d8207822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 } diff --git a/corim/profile.go b/corim/profile.go new file mode 100644 index 00000000..7c554bfb --- /dev/null +++ b/corim/profile.go @@ -0,0 +1,188 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package corim + +import ( + "encoding/json" + "errors" + "fmt" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/encoding" + "github.com/veraison/corim/extensions" +) + +// IProfileValue is the interface implemented by all types whose instances can +// be used as Profile values. +type IProfileValue interface { + extensions.ITypeChoiceValue +} + +// Profile is an identification of a CoRIM profile that defines which of the +// optional parts of CoRIM are required, which are prohibited, and which +// extension points are exercised and how. +type Profile struct { + Value IProfileValue +} + +// NewProfile instantiates a new Profile using the provided value and type +// name. +func NewProfile(val any, typ string) (*Profile, error) { + factory, ok := profileValueRegister[typ] + if !ok { + return nil, fmt.Errorf("unknown profile type: %s", typ) + } + + return factory(val) +} + +// NewProfileFromString creates a new Profile based on the provided string. The +// string must be either a valid absolute OID or a valid absolute URI. +func NewProfileFromString(uriOrOID string) (*Profile, error) { + oid, err := comid.NewTaggedOID(uriOrOID) + if err == nil { + return &Profile{oid}, nil + } + + return NewURIProfile(uriOrOID) +} + +// Type returns the type of the underlying value. +func (o Profile) Type() string { + return o.Value.Type() +} + +// String returns a string representation of the Profile's value. +func (o Profile) String() string { + return o.Value.String() +} + +// Valid returns an error if the underlying profile value is invalid +func (o *Profile) Valid() error { + if o.IsNil() { + return errors.New("cannot be nil") + } + + return o.Value.Valid() +} + +// IsNil returns true if the *Profile is nil or if its innver value is nil. +func (o *Profile) IsNil() bool { + return o == nil || o.Value == nil +} + +func (o Profile) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.Value) +} + +func (o *Profile) UnmarshalCBOR(data []byte) error { + return dm.Unmarshal(data, &o.Value) +} + +func (o Profile) MarshalJSON() ([]byte, error) { + return extensions.TypeChoiceValueMarshalJSON(o.Value) +} + +func (o *Profile) UnmarshalJSON(data []byte) error { + var tnv encoding.TypeAndValue + if err := json.Unmarshal(data, &tnv); err != nil { + return err + } + + decoded, err := NewProfile(nil, tnv.Type) + if err != nil { + return err + } + + if err := json.Unmarshal(tnv.Value, &decoded.Value); err != nil { + return fmt.Errorf("unmarshalling %s: %w", tnv.Type, err) + } + + if err := decoded.Value.Valid(); err != nil { + return fmt.Errorf("invalid %s: %w", tnv.Type, err) + } + + o.Value = decoded.Value + + return nil +} + +// NewURIProfile instantiate a new Profile based on the provided value, which +// must be convertible into a valid URI. +func NewURIProfile(val any) (*Profile, error) { + uri, err := comid.NewTaggedURI(val) + if err != nil { + return nil, err + } + + return &Profile{uri}, nil +} + +// MustNewURIProfile is like MustNewURIProfile except it panics on error. +func MustNewURIProfile(val any) *Profile { + ret, err := NewURIProfile(val) + if err != nil { + panic(err) + } + + return ret +} + +// NewOIDProfile instantiates a new Profile based on the provided value, which +// must be convertible into a valid OID. +func NewOIDProfile(val any) (*Profile, error) { + oid, err := comid.NewTaggedOID(val) + if err != nil { + return nil, err + } + + return &Profile{oid}, nil +} + +// MustNewOIDProfile is like MustNewOIDProfile except it panics on error. +func MustNewOIDProfile(val any) *Profile { + ret, err := NewOIDProfile(val) + if err != nil { + panic(err) + } + + return ret +} + +// IProfileFactory defines the signature for the factory functions that may be +// registered using RegisterProfileType to provide a new implementation of the +// corresponding type choice. The factory function should create a new *Profile +// with the underlying value created based on the provided input. The range of +// valid inputs is up to the specific type choice implementation, however it +// _must_ accept nil as one of the inputs, and return the Zero value for +// implemented type. +// See also https://go.dev/ref/spec#The_zero_value +type IProfileFactory func(any) (*Profile, error) + +var profileValueRegister = map[string]IProfileFactory{ + comid.OIDType: NewOIDProfile, + comid.URIType: NewURIProfile, +} + +// RegisterProfileType registers a new IProfileValue implementation (created +// by the provided IProfileFactory) under the specified CBOR tag. +func RegisterProfileType(tag uint64, factory IProfileFactory) error { + nilVal, err := factory(nil) + if err != nil { + return err + } + + typ := nilVal.Type() + if _, exists := profileValueRegister[typ]; exists { + return fmt.Errorf("profile type with name %q already exists", typ) + } + + if err := registerCORIMTag(tag, nilVal.Value); err != nil { + return err + } + + profileValueRegister[typ] = factory + + return nil +} diff --git a/corim/profile_test.go b/corim/profile_test.go new file mode 100644 index 00000000..7db56eb4 --- /dev/null +++ b/corim/profile_test.go @@ -0,0 +1,138 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package corim + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/veraison/corim/comid" +) + +func TestNewProfileFromString(t *testing.T) { + profile, err := NewProfileFromString("1.2.3.4") + assert.NoError(t, err) + assert.Equal(t, comid.OIDType, profile.Type()) + + profile, err = NewProfileFromString("http://example.com") + assert.NoError(t, err) + assert.Equal(t, comid.URIType, profile.Type()) + + _, err = NewProfileFromString("http://[::") + assert.ErrorContains(t, err, "missing ']' in host") +} + +func TestNewXXXProfile(t *testing.T) { + profile, err := NewURIProfile("http://[::") + assert.Nil(t, profile) + assert.ErrorContains(t, err, "missing ']' in host") + + assert.Panics(t, func() { MustNewURIProfile("http://[::") }) + + profile, err = NewURIProfile("http://example.com") + assert.NotNil(t, profile) + assert.NoError(t, err) + + profile, err = NewOIDProfile("foo") + assert.Nil(t, profile) + assert.ErrorContains(t, err, "invalid OID") + + assert.Panics(t, func() { MustNewOIDProfile("foo") }) + + profile, err = NewURIProfile("1.2.3.4") + assert.NotNil(t, profile) + assert.NoError(t, err) +} + +func TestNewProfile_unknown_type(t *testing.T) { + _, err := NewProfile("foo", "test") + assert.ErrorContains(t, err, "unknown profile") +} + +func TestProfile_UnmarshalJSON_bad(t *testing.T) { + testCases := []struct { + title string + text string + err string + }{ + { + title: "invalid JSON", + text: "foo", + err: "invalid character 'o'", + }, + { + title: "invalid type-and-value", + text: `"foo"`, + err: "cannot unmarshal string into Go value of type struct", + }, + { + title: "unknown type", + text: `{"type": "foo", "value": "bar"}`, + err: "unknown profile type: foo", + }, + { + title: "bad value", + text: `{"type": "oid", "value": "foo"}`, + err: "invalid OID", + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + var decoded Profile + err := decoded.UnmarshalJSON([]byte(tc.text)) + assert.ErrorContains(t, err, tc.err) + }) + } +} + +type intProfile int + +func (o intProfile) String() string { + return fmt.Sprint(int(o)) +} + +func (o intProfile) Valid() error { + return nil +} + +func (o intProfile) Type() string { + return "int" +} + +func badNewIntProfile(val any) (*Profile, error) { + switch t := val.(type) { + case int: + ret := intProfile(t) + return &Profile{&ret}, nil + default: + return nil, fmt.Errorf("invalid value for int profile: %v (%T)", t, t) + } +} + +func newIntProfile(val any) (*Profile, error) { + if val == nil { + ret := intProfile(0) + return &Profile{&ret}, nil + } + + return badNewIntProfile(val) +} + +func TestRegisterProfileType(t *testing.T) { + tag := uint64(0xdeadbeef) + + err := RegisterProfileType(tag, badNewIntProfile) + assert.ErrorContains(t, err, "invalid value for int profile") + + err = RegisterProfileType(32, newIntProfile) + assert.ErrorContains(t, err, "tag 32 is already registered") + + err = RegisterProfileType(tag, newIntProfile) + assert.NoError(t, err) + + err = RegisterProfileType(tag, newIntProfile) + assert.ErrorContains(t, err, `name "int" already exists`) +} diff --git a/corim/profiles.go b/corim/profiles.go index 0bca926a..57ad081e 100644 --- a/corim/profiles.go +++ b/corim/profiles.go @@ -1,4 +1,4 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim @@ -11,7 +11,6 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" "github.com/veraison/go-cose" ) @@ -60,7 +59,7 @@ func UnmarshalSignedCorimFromCBOR(buf []byte) (*SignedCorim, error) { } profiled := struct { - Profile *eat.Profile `cbor:"3,keyasint,omitempty"` + Profile *Profile `cbor:"3,keyasint,omitempty"` }{} if err := dm.Unmarshal(message.Payload, &profiled); err != nil { @@ -118,7 +117,7 @@ func UnmarshalUnsignedCorimFromCBOR(buf []byte) (*UnsignedCorim, error) { } profiled := struct { - Profile *eat.Profile `cbor:"3,keyasint,omitempty"` + Profile *Profile `cbor:"3,keyasint,omitempty"` }{} if err := dm.Unmarshal(buf[3:], &profiled); err != nil { @@ -139,7 +138,7 @@ func UnmarshalUnsignedCorimFromCBOR(buf []byte) (*UnsignedCorim, error) { // unmarshaled. func UnmarshalUnsignedCorimFromJSON(buf []byte) (*UnsignedCorim, error) { profiled := struct { - Profile *eat.Profile `json:"profile,omitempty"` + Profile *Profile `json:"profile,omitempty"` }{} if err := json.Unmarshal(buf, &profiled); err != nil { @@ -193,7 +192,7 @@ func UnmarshalAndValidateUnsignedCorimFromJSON(data []byte) (*UnsignedCorim, err // UnmarshalComidFromCBOR unmarshals a comid.Comid from provided CBOR data. If // there are extensions associated with the profile specified by the data, they // will be registered with the comid.Comid before it is unmarshaled. -func UnmarshalComidFromCBOR(buf []byte, profileID *eat.Profile) (*comid.Comid, error) { +func UnmarshalComidFromCBOR(buf []byte, profileID *Profile) (*comid.Comid, error) { var ret *comid.Comid profileManifest, ok := GetProfileManifest(profileID) @@ -213,7 +212,7 @@ func UnmarshalComidFromCBOR(buf []byte, profileID *eat.Profile) (*comid.Comid, e // UnmarshalComidFromJSON unmarshals a comid.Comid from provided JSON data. If // there are extensions associated with the profile specified by the data, they // will be registered with the comid.Comid before it is unmarshaled. -func UnmarshalComidFromJSON(buf []byte, profileID *eat.Profile) (*comid.Comid, error) { +func UnmarshalComidFromJSON(buf []byte, profileID *Profile) (*comid.Comid, error) { var ret *comid.Comid profileManifest, ok := GetProfileManifest(profileID) @@ -233,7 +232,7 @@ func UnmarshalComidFromJSON(buf []byte, profileID *eat.Profile) (*comid.Comid, e // GetSingedCorim returns a pointer to a new SingedCorim instance. If there // are extensions associated with the provided profileID, they will be // registered with the instance. -func GetSignedCorim(profileID *eat.Profile) *SignedCorim { +func GetSignedCorim(profileID *Profile) *SignedCorim { var ret *SignedCorim if profileID == nil { @@ -262,7 +261,7 @@ func GetSignedCorim(profileID *eat.Profile) *SignedCorim { // GetUnsignedCorim returns a pointer to a new UnsignedCorim instance. If there // are extensions associated with the provided profileID, they will be // registered with the instance. -func GetUnsignedCorim(profileID *eat.Profile) *UnsignedCorim { +func GetUnsignedCorim(profileID *Profile) *UnsignedCorim { var ret *UnsignedCorim if profileID == nil { @@ -292,7 +291,7 @@ func GetUnsignedCorim(profileID *eat.Profile) *UnsignedCorim { // obtaining new CoRIM and CoMID structures that had associated extensions // registered. type ProfileManifest struct { - ID *eat.Profile + ID *Profile MapExtensions extensions.Map } @@ -341,12 +340,13 @@ func (o *ProfileManifest) registerExtensions(e iextensible, points []extensions. // RegisterProfile registers a set of extensions with the specified profile. If // the profile has already been registered, or if the extensions are invalid, // an error is returned. -func RegisterProfile(id *eat.Profile, exts extensions.Map) error { - strID, err := id.Get() - if err != nil { +func RegisterProfile(id *Profile, exts extensions.Map) error { + if err := id.Valid(); err != nil { return err } + strID := id.String() + if _, ok := profilesRegister[strID]; ok { return fmt.Errorf("profile with id %q already registered", strID) } @@ -369,15 +369,12 @@ func RegisterProfile(id *eat.Profile, exts extensions.Map) error { // UnregisterProfile ensures there are no extensions registered for the // specified profile ID. Returns true if extensions were previously registered // and have been removed, and false otherwise. -func UnregisterProfile(id *eat.Profile) bool { - if id == nil { +func UnregisterProfile(id *Profile) bool { + if id.IsNil() { return false } - strID, err := id.Get() - if err != nil { - return false - } + strID := id.String() if _, ok := profilesRegister[strID]; ok { delete(profilesRegister, strID) @@ -390,17 +387,12 @@ func UnregisterProfile(id *eat.Profile) bool { // GetProfileManifest returns the ProfileManifest associated with the specified ID, or an empty // profileManifest if no ProfileManifest has been registered for the id. The second return // value indicates whether a profileManifest for the ID has been found. -func GetProfileManifest(id *eat.Profile) (ProfileManifest, bool) { - if id == nil { - return ProfileManifest{}, false - } - - strID, err := id.Get() - if err != nil { +func GetProfileManifest(id *Profile) (ProfileManifest, bool) { + if id.IsNil() { return ProfileManifest{}, false } - prof, ok := profilesRegister[strID] + prof, ok := profilesRegister[id.String()] return prof, ok } diff --git a/corim/profiles_test.go b/corim/profiles_test.go index 2cd7d269..c3daccd6 100644 --- a/corim/profiles_test.go +++ b/corim/profiles_test.go @@ -9,23 +9,20 @@ import ( "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" ) func TestProfileManifest_registration(t *testing.T) { exts := extensions.NewMap() - err := RegisterProfile(&eat.Profile{}, exts) - assert.EqualError(t, err, "no valid EAT profile") + err := RegisterProfile(&Profile{}, exts) + assert.EqualError(t, err, "cannot be nil") - p1, err := eat.NewProfile("1.2.3") - require.NoError(t, err) + p1 := MustNewOIDProfile("1.2.3") err = RegisterProfile(p1, exts) assert.NoError(t, err) - p2, err := eat.NewProfile("1.2.3") - require.NoError(t, err) + p2 := MustNewOIDProfile("1.2.3") err = RegisterProfile(p2, exts) assert.EqualError(t, err, `profile with id "1.2.3" already registered`) @@ -44,11 +41,10 @@ func TestProfileManifest_registration(t *testing.T) { assert.True(t, ok) assert.Equal(t, exts, prof.MapExtensions) - _, ok = GetProfileManifest(&eat.Profile{}) + _, ok = GetProfileManifest(&Profile{}) assert.False(t, ok) - p3, err := eat.NewProfile("2.3.4") - require.NoError(t, err) + p3 := MustNewOIDProfile("2.3.4") exts2 := extensions.NewMap().Add(extensions.Point("test"), &struct{}{}) err = RegisterProfile(p3, exts2) @@ -62,8 +58,7 @@ func TestProfileManifest_registration(t *testing.T) { } func TestProfileManifest_getters(t *testing.T) { - id, err := eat.NewProfile("1.2.3") - require.NoError(t, err) + id := MustNewOIDProfile("1.2.3") profileManifest := ProfileManifest{ ID: id, @@ -97,14 +92,13 @@ func TestProfileManifest_Marshaling_UnMarshaling(t *testing.T) { Timestamp *int `cbor:"-1,keyasint,omitempty" json:"timestamp,omitempty"` } - profID, err := eat.NewProfile("http://example.com/test-profile") - require.NoError(t, err) + profID := MustNewURIProfile("http://example.com/test-profile") extMap := extensions.NewMap(). Add(ExtUnsignedCorim, &corimExtensions{}). Add(comid.ExtEntity, &entityExtensions{}). Add(comid.ExtReferenceValue, &refValExtensions{}) - err = RegisterProfile(profID, extMap) + err := RegisterProfile(profID, extMap) require.NoError(t, err) defer UnregisterProfile(profID) @@ -131,8 +125,7 @@ func TestProfileManifest_Marshaling_UnMarshaling(t *testing.T) { Val.MustGetInt("timestamp") assert.Equal(t, 1720782190, ts) - unregProfID, err := eat.NewProfile("http://example.com") - require.NoError(t, err) + unregProfID := MustNewURIProfile("http://example.com") cmdNoExt, err := UnmarshalComidFromCBOR(c.Tags[0].Content, unregProfID) assert.NoError(t, err) @@ -200,8 +193,7 @@ func TestGetSignedCorim_NilProfile(t *testing.T) { } func TestGetSignedCorim_UnregisteredProfile(t *testing.T) { - profID, err := eat.NewProfile("http://unregistered.example.com") - require.NoError(t, err) + profID := MustNewURIProfile("http://unregistered.example.com") s := GetSignedCorim(profID) assert.NotNil(t, s) @@ -213,8 +205,7 @@ func TestGetUnsignedCorim_NilProfile(t *testing.T) { } func TestGetUnsignedCorim_UnregisteredProfile(t *testing.T) { - profID, err := eat.NewProfile("http://unregistered.example.com") - require.NoError(t, err) + profID := MustNewURIProfile("http://unregistered.example.com") c := GetUnsignedCorim(profID) assert.NotNil(t, c) diff --git a/corim/testcases/corim-ext.json b/corim/testcases/corim-ext.json index a04fbe7c..6f5407b1 100644 --- a/corim/testcases/corim-ext.json +++ b/corim/testcases/corim-ext.json @@ -1,6 +1,9 @@ { "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", - "profile": "http://example.com/test-profile", + "profile": { + "type": "uri", + "value": "http://example.com/test-profile" + }, "ext1": "foo", "validity": { "not-before": "2021-12-31T00:00:00Z", diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor index cd46e64c3b68a0dd40ad5490d29ebcb937ee846b..8b07a037c89758650e9386b5ba02497d2fce4f7b 100644 GIT binary patch delta 154 zcmV;L0A>Hq1kePKXBVc~0rjK+ZFFUGbRc7Ia%pWKX=DS~Ad}_+CXsAtk>(~80c1f# zO+^A~Qg2~oQgv=_Wpaa}lOh2wlTiT_lTZOflTiU2lcE72NFW+!kqvHGK=vr;BYxwN z12Ya*o5V~_(*o3z4*Iq;mLGQmBpf(NTQMT0-Jf34{30IOn)p3d-`T%WoWpi{O zV{dY4Z6Il61Cf3vLRdiN-fg{oISoV{U`cqN?9nry3~Od%eer6$)&*U~I%}JO%8hIr eS{_7fh=J`xL-~d_&Rql)wn8<#Dm#N9WU1~O`9?+n diff --git a/corim/testcases/signed-example-corim.cbor b/corim/testcases/signed-example-corim.cbor index 3a17eff5c3bc990d835db3db3dc083df32edb9c2..cbef4ecd91ea2f51f822166b8ef5ed349e41630c 100644 GIT binary patch delta 207 zcmcb}a+zgizaViREtrNN}I@=hfP_8Lx$~hzbAZ!)+S=pv-@{(w=8qL$(TF+ ww#&x#u6#j9?|pTg;kf;~{5l2OtkpABk0{n_zfW>IXnb$gvb|AuZ6Ddp00aJ1TmS$7 delta 206 zcmcc2a*<_%ngHWLsmze{28M%DiRU~R7#2-@FEPElQj1FzlJko)a}_dEm?ze0Iz>3#D4+9g^$gZ|ADhG{E-P5SzSu8UoTcIC vn%;LW{S1z9$enSiEbj@@QVyB8EuHZ|=o!Aa{g%7!cm2;-+qB6wY;ivT#U4}x diff --git a/corim/testcases/signed-good-corim.cbor b/corim/testcases/signed-good-corim.cbor index d21c888f9ae9f03802c6c1a807c99b041537150f..b133c90d1d10df9a76f663a03ad9932cabdce2b8 100644 GIT binary patch delta 239 zcmVAarPSbZ~PzFE3$ZZDlTH zcwudDY-ONX0b1Ds^`Zc6bY*jNAY*TGX>A~BWC4NM0s2`1KcoOX7bB=L!524%GF@Tqu9f pK-&_%JRTTkG~%9}j!n5N-$3q_H7kLPp9#m3!{lf_csD(tjb%7KVOIbE delta 221 zcmV<303!c{1cU^TCJ+F@8ez^z00F@oX;f!`0HTph7%BjCK|@VNAWU>*AaiMFZfS03 zAZulLpjiQ0*#Y&U0fE^8`dI-#qymAXk*6aW0)qen0tBG|fr6m`qXKGDZ((Fob#88D za*@(3lL`TRlMDeIla2u)Yh`6_Ek{BDp#V@ryW@W@VM{Zv(^Jp!LW>pvZFFUGbRc7I za%pWKX=GSHyI+j;n|nRm2~jtqXy$+8gY1#i_v1%0&hzd>hfW*&HSVp=Kayn_-EYom Xc+-brlaLE6w)ra;94IUFVz+Zx-B4L6 diff --git a/corim/testcases/src/corim-with-extensions.yaml b/corim/testcases/src/corim-with-extensions.yaml index b6567687..c3ab02c8 100644 --- a/corim/testcases/src/corim-with-extensions.yaml +++ b/corim/testcases/src/corim-with-extensions.yaml @@ -6,7 +6,9 @@ tag: 501 value: 0: test corim id - 3: http://example.com/test-profile + 3: + tag: 32 + value: http://example.com/test-profile -1: foo 1: - tag: 506 diff --git a/corim/testcases/src/example-corim.yaml b/corim/testcases/src/example-corim.yaml index de35a82a..a016bbd9 100644 --- a/corim/testcases/src/example-corim.yaml +++ b/corim/testcases/src/example-corim.yaml @@ -9,7 +9,9 @@ tag: 501 value: 0: test corim id - 3: http://example.com/example-profile + 3: + tag: 32 + value: http://example.com/example-profile -1: foo 1: - tag: 506 diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor index f6421f7936a5b9aa33421f53d51224debf5002e8..a65ecd8106ff937340fb5639dd5ae6f945459785 100644 GIT binary patch delta 86 zcmV-c0IC0^1EvEj*#Y&WAY*24ZvlbX0s2`1Vx$6rqymEg0RkXzF)}kCMqz7ZAX9X5 sWo2}cHzNoHp#Xt`p#YK`9+zz3YjU)Hxwqa8fh>tWC(EH{kUE)(c5_4<>2!l zow~)C8kZ>K8yXoaxFu$%Dg>7lrKXllw9=4bS;)}Xw2)yjV~V4*uPaklP<~=cP-$LX zYSF}MHLm7GjEfYc(kAleO+2k8&TvCD!0>m}ekME5Nvr&({yZ39{R^mIvIwJ^G(;6v U6_foL^`)?AWk^lU({*CdFJVYc&C_*vVqC}&;Jo{Byk?CYxFQ<9>`vXG&%X(7X6#uShcQ&v!ZVoFeHUS4X^#AG$b=7~3DCoWZ( Wc(a->wwLB~UT@TE7B5Ns_8gmHEL?JNl8)dj@4?ZY@VbFX=T6 diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index f6334bb2..e3ef7321 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -1,4 +1,4 @@ -// Copyright 2021-2024 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package corim @@ -19,7 +19,6 @@ import ( "github.com/veraison/corim/extensions" "github.com/veraison/corim/comid" - "github.com/veraison/eat" "github.com/veraison/swid" ) @@ -35,11 +34,11 @@ type UnsignedCorim struct { // if a field is optional, so we use it during unmarshaling as well as // marshaling. Hence omitempty is present for the json tag, but not // cbor. - Tags []Tag `cbor:"1,keyasint" json:"tags,omitempty"` - DependentRims *[]Locator `cbor:"2,keyasint,omitempty" json:"dependent-rims,omitempty"` - Profile *eat.Profile `cbor:"3,keyasint,omitempty" json:"profile,omitempty"` - RimValidity *Validity `cbor:"4,keyasint,omitempty" json:"validity,omitempty"` - Entities *Entities `cbor:"5,keyasint,omitempty" json:"entities,omitempty"` + Tags []Tag `cbor:"1,keyasint" json:"tags,omitempty"` + DependentRims *[]Locator `cbor:"2,keyasint,omitempty" json:"dependent-rims,omitempty"` + Profile *Profile `cbor:"3,keyasint,omitempty" json:"profile,omitempty"` + RimValidity *Validity `cbor:"4,keyasint,omitempty" json:"validity,omitempty"` + Entities *Entities `cbor:"5,keyasint,omitempty" json:"entities,omitempty"` Extensions } @@ -178,7 +177,7 @@ func (o *UnsignedCorim) AddDependentRim(href string, thumbprint *swid.HashEntry) // the profile in the unsigned-corim-map func (o *UnsignedCorim) SetProfile(urlOrOID string) *UnsignedCorim { if o != nil { - p, err := eat.NewProfile(urlOrOID) + p, err := NewProfileFromString(urlOrOID) if err != nil { return nil } @@ -429,7 +428,7 @@ func (o UnsignedCorim) Valid() error { } if o.Profile != nil { - if err := ValidProfile(*o.Profile); err != nil { + if err := o.Profile.Valid(); err != nil { return fmt.Errorf("profile validation failed: %w", err) } } @@ -598,12 +597,3 @@ func (o Locator) Valid() error { return nil } - -// ValidProfile checks that the supplied profile is in one of the supported -// formats (i.e., URI or OID) -func ValidProfile(p eat.Profile) error { - if !p.IsOID() && !p.IsURI() { - return errors.New("profile should be OID or URI") - } - return nil -} diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 2dbb0d5d..10014199 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -358,7 +358,10 @@ func TestUnsignedCorim_ToJSON(t *testing.T) { "corim-id":"invalid.tags.corim", "tags":["2QH6WOuiAaEAdXZlbmRvci5leGFtcGxlL3Byb2QvMQShA4GCoQHYJVAx+1q/Aj5JkqpOlfnBUDv6gdkCKnixLS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUZrd0V3WUhLb1pJemowQ0FRWUlLb1pJemowREFRY0RRZ0FFVzFCdnFGKy9yeThCV2E3WkVNVTF4WVlIRVE4QgpsTFQ0TUZIT2FPK0lDVHRJdnJFZUVwci9zZlRBUDY2SDJoQ0hkYjVIRVhLdFJLb2Q2UUxjT0xQQTFRPT0KLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0t"], "dependent-rims":[{"href":"http://endorser.example/addon.corim"}], - "profile":"https://arm.com/psa/iot/2.0.0" + "profile":{ + "type": "uri", + "value": "https://arm.com/psa/iot/2.0.0" + } } ` diff --git a/profiles/cca/corim_test.go b/profiles/cca/corim_test.go index fce07476..5cc9b65e 100644 --- a/profiles/cca/corim_test.go +++ b/profiles/cca/corim_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" - "github.com/veraison/eat" ) // getTestcasePath returns the absolute path to a testcase file @@ -49,9 +48,7 @@ func getComidFromCorimWithProfile(t *testing.T, corimData []byte, profileURI str require.Greater(t, len(c.Tags), 0, "CoRIM must have at least one tag") // Get a CoMID with the specified profile extensions registered - profileID, err := eat.NewProfile(profileURI) - require.NoError(t, err) - + profileID := corim.MustNewURIProfile(profileURI) manifest, found := corim.GetProfileManifest(profileID) require.True(t, found, "profile %s should be registered", profileURI) diff --git a/profiles/cca/platform.go b/profiles/cca/platform.go index 902a616a..83f90511 100644 --- a/profiles/cca/platform.go +++ b/profiles/cca/platform.go @@ -10,7 +10,6 @@ import ( "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" "github.com/veraison/corim/profiles/psa" - "github.com/veraison/eat" ) const PlatformProfileURI = "tag:arm.com,2025:cca_platform#1.0.0" @@ -22,10 +21,7 @@ const ( ) func init() { - profileID, err := eat.NewProfile(PlatformProfileURI) - if err != nil { - panic(err) - } + profileID := corim.MustNewURIProfile(PlatformProfileURI) extMap := extensions.NewMap(). Add(comid.ExtTriples, &PlatformTriplesExtensions{}) diff --git a/profiles/cca/realm.go b/profiles/cca/realm.go index 0a938410..55dabbd4 100644 --- a/profiles/cca/realm.go +++ b/profiles/cca/realm.go @@ -9,7 +9,6 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" ) const RealmProfileURI = "tag:arm.com,2025:cca_realm#1.0.0" @@ -25,10 +24,7 @@ const ( ) func init() { - profileID, err := eat.NewProfile(RealmProfileURI) - if err != nil { - panic(err) - } + profileID := corim.MustNewURIProfile(RealmProfileURI) extMap := extensions.NewMap(). Add(comid.ExtTriples, &RealmTriplesExtensions{}) diff --git a/profiles/cca/testcases/cca-platform-invalid-digests.cbor b/profiles/cca/testcases/cca-platform-invalid-digests.cbor index 4197e2705bb8359f88dd97f20e34028f2ebbe6ca..d406dfed5ff2e4fc46b1765d8d6d545e9b4dff13 100644 GIT binary patch delta 34 qcmZo;>R_6n!gNDnqDt|^-Kso1C9pke`>DSHifMJ2NQQHQvd`Fy3flfbYZu M3QRW?CO%LF04Zw}DgXcg delta 33 pcmZo?YGs<>$HcgJVzDnbcVVZ6~qKZ%J46ed1W1puqF3jP29 diff --git a/profiles/cca/testcases/cca-platform-valid.cbor b/profiles/cca/testcases/cca-platform-valid.cbor index 669513a626c63e65847e97e53292fff7d7274e88..8f7b67cb6cb7b4020435cd1ccaa06d51299a555b 100644 GIT binary patch delta 57 zcmV-90LK6S1c3#RAOhGRksvM<0igg;KL)SOtekU{d6xiCR*-i;kxojHzcG_K0Ve^X PlL-M>k?|O_VF3yORi6|V delta 50 zcmV-20L}k_1^)z)Adw<21_Yu2fr6ovdjTksk{prWCy}l(0iu&%0T_|22$K&1P_uUd I3IUN&6vhe=`2YX_ diff --git a/profiles/cca/testcases/cca-realm-invalid-missing-rim.cbor b/profiles/cca/testcases/cca-realm-invalid-missing-rim.cbor index 266597ce0128fda105af57025294bde4a6365a55..76b5191b7ba3f291982cb6f6822edfb87e8bada9 100644 GIT binary patch delta 19 bcmX@jc$SgnCgaz|6V;b6Gv82{$f^VYPHqPy delta 16 YcmX@hc$$&*Cgaz|%oA5DO=MXD06857tN;K2 diff --git a/profiles/cca/testcases/cca-realm-invalid-rim-size.cbor b/profiles/cca/testcases/cca-realm-invalid-rim-size.cbor index 0a75fbdc723be0dcd422822b986c55f6c3b3f03b..cd87828bd0a8bece0afd96b5524caac00e53e0a5 100644 GIT binary patch delta 25 gcmdnSxPx(m2Gb3Ni5jt-4C%?qiF!quxf5G80BukRga7~l delta 22 dcmdnNxQ%gw#zd{yi4!z98Pb!J6ZMKRa{*aO2bTZ< diff --git a/profiles/cca/testcases/cca-realm-valid.cbor b/profiles/cca/testcases/cca-realm-valid.cbor index 2f60121a3499140a8b66f1ce921922be747e63e3..0b06444cb54ca2b67209deb15762b684ecfcbfda 100644 GIT binary patch delta 91 zcmX@ce1e(fCgaz|lh-qfGc(^%n8>Ov!nlwjz@BaW*)?;Dr&La72nd@{Za>k$fRiCT nIXO|UC^L6rf(GNFiE}i#88W~ksksJ=CZ5;ef^!WgeqRCr)_oxp delta 106 zcmX@Xe2kg(Cgaz|%oF9bC&n5~oTeedke-~Js8^JkyNDqJ%u3BQn7Chq8^JZ4_(@~3 y3gZ%O#)S+4_H66Vu9;IjrE)q$K-h$Gdxlb=Cf%ac#GG8+vc#Os6y4-}pkV+RStlj{ diff --git a/profiles/cca/testcases/no-profile.cbor b/profiles/cca/testcases/no-profile.cbor index 21f2d10ef801e888d418ca701199ffcace5e45ed..ff80a2dd713d38fe93c07da010032700fdc76649 100644 GIT binary patch delta 42 ycmcb@c!iPWCgayd6UBTc?vY?*oY-YFF-2X7p&&0`x1cCLEi)%oH#xs3GZz3@CJ(0o delta 42 ycmcb@c!g2yCgayd3d6M&cq5G0j8{={KS-?(!9LXqQ*sxOD0}2n7Beqf}yOSI8m=SKdq!Zu_#qH NIX|}`KQA?}1ORV&77qXb delta 58 zcmbQmG>d6M4i8gSP<~=cP-$LXYSF|T9hSyLj7uio(3-fyK!TyHpg2*lI6tkVJh3QM OH#tAIAU`iPuLJ;dycP)n diff --git a/profiles/psa/testcases/psa-invalid-attest-key.cbor b/profiles/psa/testcases/psa-invalid-attest-key.cbor index def726c901555c2b8383d91125401d07dedbf661..36cb2473c5c9c10334a964c0c0907d32efc87757 100644 GIT binary patch delta 96 zcmaFN@|0zQ3eydRi7L)cj5icYGD=DcimmkZ6O(gO^-?Pma|?1(nVK0GnHVx1oqb&u zd`eRESQaujHZ5XU%#;T0s*#Y&V0fE^8`dIv=1fc+df}sGT0NDaCSRaw#7LlM23<7FWZ((Fo zb#88Da*?kbkrWMJpcwco}eW gVJ&HHc42I3WG!iJaBMAUWG!QFa%pV?*dUQ198It<@Bjb+ delta 104 zcmV-u0GI#50>Ap!m@~=v1uX0Vur~V8C69XQyiUrU750i@)J{nO7rqkiy9YA oKFJt6c@v{JJ9A^xBB(-1Oofa$nbaZ_87Dtr6q~r=&_q600K}X%wg3PC delta 128 zcmcc0a+!tqCgaz|jEy%Le?>A$E}6{F{KS-?(!9LX zqKQA%CmS)|6Krf+$gmhyLPx4`5kpx)aiU&vep*R+Vo|DYa(-?>eqL%`$;A3t!F-^U TZdqbZW(rWeC^MINqJ#_pE!8kq diff --git a/profiles/psa/testcases/src/psa-invalid-attest-key.yaml b/profiles/psa/testcases/src/psa-invalid-attest-key.yaml index 8c285660..f0c557c0 100644 --- a/profiles/psa/testcases/src/psa-invalid-attest-key.yaml +++ b/profiles/psa/testcases/src/psa-invalid-attest-key.yaml @@ -8,7 +8,9 @@ tag: 501 value: 0: psa-invalid-attest-key-corim - 3: "tag:arm.com,2025:psa#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:psa#1.0.0" 1: - tag: 506 value: diff --git a/profiles/psa/testcases/src/psa-invalid-impl-id.yaml b/profiles/psa/testcases/src/psa-invalid-impl-id.yaml index c3390325..ce1efdf8 100644 --- a/profiles/psa/testcases/src/psa-invalid-impl-id.yaml +++ b/profiles/psa/testcases/src/psa-invalid-impl-id.yaml @@ -6,7 +6,9 @@ tag: 501 value: 0: psa-invalid-impl-id-corim - 3: "tag:arm.com,2025:psa#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:psa#1.0.0" 1: - tag: 506 value: diff --git a/profiles/psa/testcases/src/psa-valid.yaml b/profiles/psa/testcases/src/psa-valid.yaml index a8324a66..73470c0b 100644 --- a/profiles/psa/testcases/src/psa-valid.yaml +++ b/profiles/psa/testcases/src/psa-valid.yaml @@ -7,7 +7,9 @@ tag: 501 value: 0: psa-valid-corim - 3: "tag:arm.com,2025:psa#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:psa#1.0.0" 1: - tag: 506 value: diff --git a/profiles/tdx/example_pce_refval_test.go b/profiles/tdx/example_pce_refval_test.go index 897594c7..0144e5d6 100644 --- a/profiles/tdx/example_pce_refval_test.go +++ b/profiles/tdx/example_pce_refval_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -9,15 +9,11 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" - "github.com/veraison/eat" ) // Example_decode_PCE_JSON decodes the TDX Provisioning Certification Enclave Measurement Extensions from the given JSON Template func Example_decode_PCE_JSON() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") @@ -95,10 +91,7 @@ func extractPCERefVals(c *comid.Comid) error { } func Example_decode_PCE_CBOR() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") @@ -162,10 +155,7 @@ func Example_decode_PCE_CBOR() { } func Example_encode_tdx_pce_refval_with_profile() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") @@ -173,9 +163,6 @@ func Example_encode_tdx_pce_refval_with_profile() { } m := manifest.GetComid() - if m == nil { - panic(err) - } m.SetTagIdentity("43BBE37F-2E61-4B33-AED3-53CFF1428B20", 0). AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) @@ -190,7 +177,7 @@ func Example_encode_tdx_pce_refval_with_profile() { refVal.Measurements.Add(measurement) m.Triples.AddReferenceValue(refVal) - err = SetTdxPceMvalExtensions(ReferenceValue, &m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) + err := SetTdxPceMvalExtensions(ReferenceValue, &m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) if err != nil { fmt.Printf("unable to set extensions :%s", err.Error()) } diff --git a/profiles/tdx/example_qe_refval_test.go b/profiles/tdx/example_qe_refval_test.go index e78470d0..c9cf43e8 100644 --- a/profiles/tdx/example_qe_refval_test.go +++ b/profiles/tdx/example_qe_refval_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -10,15 +10,11 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" ) // Example_decode_QE_JSON decodes the TDX Quoting Enclave Measurement Extensions from the given JSON Template func Example_decode_QE_JSON() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") @@ -123,10 +119,7 @@ func Example_encode_tdx_QE_refval_without_profile() { } func Example_decode_QE_CBOR() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") diff --git a/profiles/tdx/example_seam_refval_test.go b/profiles/tdx/example_seam_refval_test.go index 284a20e8..14547257 100644 --- a/profiles/tdx/example_seam_refval_test.go +++ b/profiles/tdx/example_seam_refval_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -11,15 +11,11 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" ) // Example_decode_JSON decodes the TDX Measurement Extensions from the given JSON Template func Example_decode_JSON() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") @@ -110,10 +106,7 @@ func Example_encode_tdx_seam_refval_without_profile() { } func Example_encode_tdx_seam_refval_with_profile() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") @@ -121,9 +114,6 @@ func Example_encode_tdx_seam_refval_with_profile() { } m := manifest.GetComid() - if m == nil { - panic(err) - } m.SetTagIdentity("43BBE37F-2E61-4B33-AED3-53CFF1428B20", 0). AddEntity("INTEL", &TestRegID, comid.RoleCreator, comid.RoleTagCreator, comid.RoleMaintainer) @@ -138,7 +128,7 @@ func Example_encode_tdx_seam_refval_with_profile() { refVal.Measurements.Add(measurement) m.Triples.AddReferenceValue(refVal) - err = SetTDXSeamMvalExtensions(ReferenceValue, &m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) + err := SetTDXSeamMvalExtensions(ReferenceValue, &m.Triples.ReferenceValues.Values[0].Measurements.Values[0].Val) if err != nil { fmt.Printf("unable to set extensions :%s", err.Error()) } @@ -217,10 +207,7 @@ func Example_encode_tdx_seam_refval_direct() { } func Example_decode_CBOR() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") manifest, found := corim.GetProfileManifest(profileID) if !found { fmt.Printf("CoRIM Profile NOT FOUND") diff --git a/profiles/tdx/mval_extensions.go b/profiles/tdx/mval_extensions.go index 20f3fd52..f2f0b511 100644 --- a/profiles/tdx/mval_extensions.go +++ b/profiles/tdx/mval_extensions.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -9,7 +9,6 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/corim" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" ) // MValExtensions contains the Intel TDX profile extensions which can appear in @@ -43,10 +42,7 @@ type MValExtensions struct { // which is "joint-iso-itu-t.country.us.organization.intel.intel-comid.profile" func init() { - profileID, err := eat.NewProfile("2.16.840.1.113741.1.16.1") - if err != nil { - panic(err) // will not error, as the hard-coded string above is valid - } + profileID := corim.MustNewOIDProfile("2.16.840.1.113741.1.16.1") extMap := extensions.NewMap(). Add(comid.ExtReferenceValue, &MValExtensions{}). From 9168346692fc0908d49787550e1f031e85986f2b Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 19 May 2026 15:07:28 +0100 Subject: [PATCH 17/18] fix!: digest consistent with the spec Replace swid.HashEntry with a new Digest type. This need to accommodate text algorithm IDs supported by the CoRIM spec rev. 10[1], but not allowed by CoSWID. Note: CoRIM borrows the definition of Digest from EAT measured components spec[2]. The latter, unlike the CoRIM spec, defines a JSON representation, so the JSON serialization of digests has been changed to match that. [2]: https://datatracker.ietf.org/doc/html/draft-ietf-rats-corim-10 [2]: https://datatracker.ietf.org/doc/html/draft-ietf-rats-eat-measured-component-12 BREAKING CHANGE: swid.HashEntry is replaced with comid.Digest; their fields differ. Also JSON serialization for digests changed to be consistent with the one defined for eatmc.digest. Signed-off-by: Sergei Trofimov --- coev/example_test.go | 16 +- coev/tdx/example_qe_test.go | 4 +- coev/tdx/example_seam_test.go | 4 +- coev/tdx/test_vars.go | 10 +- comid/cryptokey.go | 72 ++--- comid/cryptokey_test.go | 53 ++-- comid/digest.go | 320 +++++++++++++++++++++++ comid/digest_test.go | 276 +++++++++++++++++++ comid/digests.go | 49 ++-- comid/digests_test.go | 125 ++++----- comid/example_test.go | 31 ++- comid/integrityregisters.go | 4 +- comid/integrityregisters_test.go | 181 +++++++------ comid/measurement.go | 2 +- comid/measurement_test.go | 4 +- comid/test_vars.go | 6 +- corim/example_profile_test.go | 5 +- corim/oneormore_test.go | 25 +- corim/unsignedcorim.go | 8 +- corim/unsignedcorim_test.go | 4 +- coserv/coserv_test.go | 17 +- profiles/cca/platform.go | 2 +- profiles/cca/platform_test.go | 9 +- profiles/cca/realm.go | 2 +- profiles/cca/realm_test.go | 7 +- profiles/tdx/example_qe_refval_test.go | 2 +- profiles/tdx/example_seam_refval_test.go | 6 +- profiles/tdx/teedigest_test.go | 3 +- profiles/tdx/test_common_methods.go | 6 +- profiles/tdx/test_qe_methods.go | 5 +- profiles/tdx/test_seam_methods.go | 7 +- profiles/tdx/test_vars.go | 10 +- 32 files changed, 902 insertions(+), 373 deletions(-) create mode 100644 comid/digest.go create mode 100644 comid/digest_test.go diff --git a/coev/example_test.go b/coev/example_test.go index a7e9e9c6..5c40ab98 100644 --- a/coev/example_test.go +++ b/coev/example_test.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package coev @@ -29,8 +29,8 @@ func Example_encode_EvidenceTriples() { comid.MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + AddDigest(comid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(comid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). SetFlagsTrue(comid.FlagIsDebug). SetFlagsFalse(comid.FlagIsSecure). SetSerialNumber("C02X70VHJHD5"). @@ -65,7 +65,7 @@ func Example_encode_EvidenceTriples() { // Output: // a300a1008182a100a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040181a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa01d8255031fb5abf023e4992aa4e95f9c1503bfa026f68747470733a2f2f6162632e636f6d - // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}]},"evidence-id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"profile":"https://abc.com"} + // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":[[6,"q83vAA"],[6,"_____w"]],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}]},"evidence-id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"profile":"https://abc.com"} } @@ -238,8 +238,8 @@ func Example_decode_JSON() { "value": 2 }, "digests": [ - "sha-256-32;q83vAA==", - "sha-256-32;/////w==" + [6, "q83vAA"], + [6, "_____w"] ], "flags": { "is-secure": false, @@ -284,8 +284,8 @@ func Example_decode_JSON() { "value": 2 }, "digests": [ - "sha-256-32;q83vAA==", - "sha-256-32;/////w==" + [6, "q83vAA"], + [6, "_____w"] ], "flags": { "is-secure": false, diff --git a/coev/tdx/example_qe_test.go b/coev/tdx/example_qe_test.go index bf9c0a99..314d7ffd 100644 --- a/coev/tdx/example_qe_test.go +++ b/coev/tdx/example_qe_test.go @@ -104,7 +104,7 @@ func Example_encode_tdx_qe_evidence_without_profile() { // output: // d9023ba100a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02703031323334353637383941424344454681a101a538480a385046c000fbff000038538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638540138550b - // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":{"type":"uint","value":10},"miscselect":"wAD7/wAA","mrsigner":{"type":"digest","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]},"isvprodid":{"type":"uint","value":1},"tcbevalnum":{"type":"uint","value":11}}}]}]}} + // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":{"type":"uint","value":10},"miscselect":"wAD7/wAA","mrsigner":{"type":"digest","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]},"isvprodid":{"type":"uint","value":1},"tcbevalnum":{"type":"uint","value":11}}}]}]}} } func Example_encode_tdx_qe_evidence_with_profile() { @@ -153,7 +153,7 @@ func Example_encode_tdx_qe_evidence_with_profile() { // output: // d9023ba100a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02703031323334353637383941424344454681a101a538480a385046c000fbff000038538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638540138550b - // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":{"type":"uint","value":10},"miscselect":"wAD7/wAA","mrsigner":{"type":"digest","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]},"isvprodid":{"type":"uint","value":1},"tcbevalnum":{"type":"uint","value":11}}}]}]}} + // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":{"type":"uint","value":10},"miscselect":"wAD7/wAA","mrsigner":{"type":"digest","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]},"isvprodid":{"type":"uint","value":1},"tcbevalnum":{"type":"uint","value":11}}}]}]}} } var ( diff --git a/coev/tdx/example_seam_test.go b/coev/tdx/example_seam_test.go index 643da822..43b48ec4 100644 --- a/coev/tdx/example_seam_test.go +++ b/coev/tdx/example_seam_test.go @@ -101,7 +101,7 @@ func Example_encode_tdx_seam_evidence_without_profile() { // output: // d9023ba100a1008182a100a300d86f4c6086480186f84d01020304030171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b - // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.3"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"uint","value":10},"attributes":"AQE=","mrtee":{"type":"digest","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]},"mrsigner":{"type":"digest","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"uint","value":11}}}]}]}} + // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.3"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"uint","value":10},"attributes":"AQE=","mrtee":{"type":"digest","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]},"mrsigner":{"type":"digest","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"uint","value":11}}}]}]}} } var ( @@ -154,7 +154,7 @@ func Example_encode_tdx_seam_evidence_with_profile() { // output: // d9023ba100a1008182a100a300d86f4c6086480186f84d01020304030171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc8038480a385142010138528182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7538538282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36385442010138550b - // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.3"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"uint","value":10},"attributes":"AQE=","mrtee":{"type":"digest","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]},"mrsigner":{"type":"digest","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"uint","value":11}}}]}]}} + // {"ev-triples":{"evidence-triples":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.3"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"uint","value":10},"attributes":"AQE=","mrtee":{"type":"digest","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]},"mrsigner":{"type":"digest","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"uint","value":11}}}]}]}} } func Example_decode_CBOR() { diff --git a/coev/tdx/test_vars.go b/coev/tdx/test_vars.go index 61c8d057..5212ec3f 100644 --- a/coev/tdx/test_vars.go +++ b/coev/tdx/test_vars.go @@ -46,8 +46,8 @@ var ( "mrsigner": { "type": "digest", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=", - "sha-512;oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"], + [8, "oxT8LcZjrnpra8Z4dZQFc5bms_VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag"] ] }, "isvprodid": { @@ -205,14 +205,14 @@ var ( "mrtee": { "type": "digest", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "mrsigner": { "type": "digest", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=", - "sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"], + [7, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"] ] }, "isvprodid": { diff --git a/comid/cryptokey.go b/comid/cryptokey.go index bbe5d383..34e1612c 100644 --- a/comid/cryptokey.go +++ b/comid/cryptokey.go @@ -17,7 +17,6 @@ import ( "github.com/veraison/corim/encoding" "github.com/veraison/corim/extensions" "github.com/veraison/go-cose" - "github.com/veraison/swid" ) const ( @@ -62,7 +61,7 @@ type CryptoKey struct { // NewCryptoKey returns the pointer to a new CryptoKey of the specified type, // constructed using the provided value k. The type of k depends on the // specified crypto key type. For PKIX types, k must be a string. For COSE_Key, -// k must be a []byte. For thumbprint types, k must be a swid.HashEntry. +// k must be a []byte. For thumbprint types, k must be a Digest. func NewCryptoKey(k any, typ string) (*CryptoKey, error) { factory, ok := cryptoKeyValueRegister[typ] if !ok { @@ -671,32 +670,11 @@ func (o TaggedPKIXAsn1DerCert) cert() (*x509.Certificate, error) { return cert, nil } -type digest struct { - swid.HashEntry -} - -func (o digest) String() string { - return o.HashEntry.String() -} - -func (o digest) Valid() error { - return swid.ValidHashEntry(o.HashAlgID, o.HashValue) -} - -func (o digest) PublicKey() (crypto.PublicKey, error) { - return nil, errors.New("cannot get PublicKey from a digest") -} - -func (o digest) Bytes() []byte { - ret, _ := em.Marshal(o) - return ret -} - // ThumbprintTypeTaggedThumbprint represents a digest of a raw public key. The // digest value may be used to find the public key if contained in a lookup // table. type TaggedThumbprint struct { - digest + Digest } // nolint:dupl @@ -705,22 +683,22 @@ func NewThumbprint(k any) (*CryptoKey, error) { return &CryptoKey{&TaggedThumbprint{}}, nil } - var he swid.HashEntry + var digest Digest var err error switch t := k.(type) { case string: - he, err = swid.ParseHashEntry(t) + digest, err = DigestFromString(t) if err != nil { - return nil, fmt.Errorf("swid.HashEntry decode error: %w", err) + return nil, fmt.Errorf("digest: %w", err) } - case swid.HashEntry: - he = t + case Digest: + digest = t default: - return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) + return nil, fmt.Errorf("value must be a Digest or a string; found %T", k) } - key := &CryptoKey{&TaggedThumbprint{digest{he}}} + key := &CryptoKey{&TaggedThumbprint{digest}} if err := key.Valid(); err != nil { return nil, err @@ -746,7 +724,7 @@ func (o TaggedThumbprint) Type() string { // TaggedCertThumbprint represents a digest of a certificate. The digest value // may be used to find the certificate if contained in a lookup table. type TaggedCertThumbprint struct { - digest + Digest } // nolint:dupl @@ -755,22 +733,22 @@ func NewCertThumbprint(k any) (*CryptoKey, error) { return &CryptoKey{&TaggedCertThumbprint{}}, nil } - var he swid.HashEntry + var digest Digest var err error switch t := k.(type) { case string: - he, err = swid.ParseHashEntry(t) + digest, err = DigestFromString(t) if err != nil { - return nil, fmt.Errorf("swid.HashEntry decode error: %w", err) + return nil, fmt.Errorf("digest: %w", err) } - case swid.HashEntry: - he = t + case Digest: + digest = t default: - return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) + return nil, fmt.Errorf("value must be a Digest or a string; found %T", k) } - key := &CryptoKey{&TaggedCertThumbprint{digest{he}}} + key := &CryptoKey{&TaggedCertThumbprint{digest}} if err := key.Valid(); err != nil { return nil, err @@ -797,7 +775,7 @@ func (o TaggedCertThumbprint) Type() string { // digest value may be used to find the certificate path if contained in a // lookup table. type TaggedCertPathThumbprint struct { - digest + Digest } // nolint:dupl @@ -806,22 +784,22 @@ func NewCertPathThumbprint(k any) (*CryptoKey, error) { return &CryptoKey{&TaggedCertPathThumbprint{}}, nil } - var he swid.HashEntry + var digest Digest var err error switch t := k.(type) { case string: - he, err = swid.ParseHashEntry(t) + digest, err = DigestFromString(t) if err != nil { - return nil, fmt.Errorf("swid.HashEntry decode error: %w", err) + return nil, fmt.Errorf("digest: %w", err) } - case swid.HashEntry: - he = t + case Digest: + digest = t default: - return nil, fmt.Errorf("value must be a swid.HashEntry or a string; found %T", k) + return nil, fmt.Errorf("value must be a Digest or a string; found %T", k) } - key := &CryptoKey{&TaggedCertPathThumbprint{digest{he}}} + key := &CryptoKey{&TaggedCertPathThumbprint{digest}} if err := key.Valid(); err != nil { return nil, err diff --git a/comid/cryptokey_test.go b/comid/cryptokey_test.go index 5a5d7ac1..c7dcde18 100644 --- a/comid/cryptokey_test.go +++ b/comid/cryptokey_test.go @@ -14,7 +14,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/swid" ) func Test_CryptoKey_NewPKIXBase64Key(t *testing.T) { @@ -169,17 +168,11 @@ func Test_CryptoKey_NewThumbprint(t *testing.T) { _, err = key.PublicKey() assert.EqualError(t, err, "cannot get PublicKey from a digest") - badAlg := swid.HashEntry{ - HashAlgID: 99, - HashValue: MustHexDecode(nil, `deadbeef`), - } + badAlg := NewDigestIntAlg(0, MustHexDecode(nil, `deadbeef`)) _, err = newFunc(badAlg) - assert.Contains(t, err.Error(), "unknown hash algorithm 99") + assert.Contains(t, err.Error(), "zero algorithm") - badHash := swid.HashEntry{ - HashAlgID: 1, - HashValue: MustHexDecode(nil, `deadbeef`), - } + badHash := NewDigestIntAlg(Sha256, MustHexDecode(nil, `deadbeef`)) _, err = newFunc(badHash) assert.Contains(t, err.Error(), "length mismatch for hash algorithm") } @@ -195,7 +188,7 @@ func Test_CryptoKey_NewThumbprint(t *testing.T) { assert.Equal(t, TestThumbprint.String(), key.String()) assert.Panics(t, func() { - mustNewFunc(swid.HashEntry{}) + mustNewFunc(Digest{}) }) } } @@ -217,50 +210,50 @@ func Test_CryptoKey_JSON_roundtrip(t *testing.T) { { Type: BytesType, In: TestTaggedBytes, - Out: base64.StdEncoding.EncodeToString(TestTaggedBytes), + Out: fmt.Sprintf("%q", base64.StdEncoding.EncodeToString(TestTaggedBytes)), }, { Type: PKIXBase64KeyType, In: TestECPubKey, - Out: TestECPubKey, + Out: fmt.Sprintf("%q", TestECPubKey), }, { Type: PKIXBase64CertType, In: TestCert, - Out: TestCert, + Out: fmt.Sprintf("%q", TestCert), }, { Type: PKIXBase64CertPathType, In: TestCertPath, - Out: TestCertPath, + Out: fmt.Sprintf("%q", TestCertPath), }, { Type: COSEKeyType, In: TestCOSEKey, - Out: base64.StdEncoding.EncodeToString(TestCOSEKey), + Out: fmt.Sprintf("%q", base64.StdEncoding.EncodeToString(TestCOSEKey)), }, { Type: ThumbprintType, In: TestThumbprint, - Out: fmt.Sprintf("%s;%s", - TestThumbprint.AlgIDToString(), - base64.StdEncoding.EncodeToString(TestThumbprint.HashValue), + Out: fmt.Sprintf("[%d,%q]", + TestThumbprint.Algorithm.Int(), + base64.RawURLEncoding.EncodeToString(TestThumbprint.Value), ), }, { Type: CertThumbprintType, In: TestThumbprint, - Out: fmt.Sprintf("%s;%s", - TestThumbprint.AlgIDToString(), - base64.StdEncoding.EncodeToString(TestThumbprint.HashValue), + Out: fmt.Sprintf("[%d,%q]", + TestThumbprint.Algorithm.Int(), + base64.RawURLEncoding.EncodeToString(TestThumbprint.Value), ), }, { Type: CertPathThumbprintType, In: TestThumbprint, - Out: fmt.Sprintf("%s;%s", - TestThumbprint.AlgIDToString(), - base64.StdEncoding.EncodeToString(TestThumbprint.HashValue), + Out: fmt.Sprintf("[%d,%q]", + TestThumbprint.Algorithm.Int(), + base64.RawURLEncoding.EncodeToString(TestThumbprint.Value), ), }, } { @@ -269,7 +262,7 @@ func Test_CryptoKey_JSON_roundtrip(t *testing.T) { data, err := json.Marshal(key) require.NoError(t, err) - expected := fmt.Sprintf(`{"type": %q, "value": %q}`, tv.Type, tv.Out) + expected := fmt.Sprintf(`{"type": %q, "value": %s}`, tv.Type, tv.Out) assert.JSONEq(t, expected, string(data)) var key2 CryptoKey @@ -305,7 +298,7 @@ func Test_CryptoKey_UnmarshalJSON_negative(t *testing.T) { }, { Val: `{"type": "thumbprint", "value":"deadbeef"}`, - ErrMsg: "bad format: expecting ;", + ErrMsg: "cannot unmarshal string into Go value of type []interface {}", }, { Val: `{"type": "random-key", "value":"deadbeef"}`, @@ -412,17 +405,17 @@ func Test_NewCryptoKey_negative(t *testing.T) { { Type: ThumbprintType, In: 7, - ErrMsg: "value must be a swid.HashEntry or a string; found int", + ErrMsg: "value must be a Digest or a string; found int", }, { Type: CertThumbprintType, In: 7, - ErrMsg: "value must be a swid.HashEntry or a string; found int", + ErrMsg: "value must be a Digest or a string; found int", }, { Type: CertPathThumbprintType, In: 7, - ErrMsg: "value must be a swid.HashEntry or a string; found int", + ErrMsg: "value must be a Digest or a string; found int", }, { Type: "random-key", diff --git a/comid/digest.go b/comid/digest.go new file mode 100644 index 00000000..73029fea --- /dev/null +++ b/comid/digest.go @@ -0,0 +1,320 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "crypto" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "strconv" + "strings" +) + +// Named Information Hash Algorithm Registry +// https://www.iana.org/assignments/named-information/named-information.xhtml#hash-alg +const ( + Sha256 int = (iota + 1) + Sha256_128 + Sha256_120 + Sha256_96 + Sha256_64 + Sha256_32 + Sha384 + Sha512 + Sha3_224 + Sha3_256 + Sha3_384 + Sha3_512 +) + +var ( + algToValueLen = map[int]int{ + Sha256: 32, + Sha256_128: 16, + Sha256_120: 15, + Sha256_96: 12, + Sha256_64: 8, + Sha256_32: 4, + Sha384: 48, + Sha512: 64, + Sha3_224: 28, + Sha3_256: 32, + Sha3_384: 48, + Sha3_512: 64, + } + + algToString = map[int]string{ + Sha256: "sha-256", + Sha256_128: "sha-256-128", + Sha256_120: "sha-256-120", + Sha256_96: "sha-256-96", + Sha256_64: "sha-256-64", + Sha256_32: "sha-256-32", + Sha384: "sha-384", + Sha512: "sha-512", + Sha3_224: "sha3-224", + Sha3_256: "sha3-256", + Sha3_384: "sha3-384", + Sha3_512: "sha3-512", + } + + stringToAlg = map[string]int{ + "sha-256": Sha256, + "sha-256-128": Sha256_128, + "sha-256-120": Sha256_120, + "sha-256-96": Sha256_96, + "sha-256-64": Sha256_64, + "sha-256-32": Sha256_32, + "sha-384": Sha384, + "sha-512": Sha512, + "sha3-224": Sha3_224, + "sha3-256": Sha3_256, + "sha3-384": Sha3_384, + "sha3-512": Sha3_512, + } +) + +func IntDigestAlgorithm(val int) DigestAlgorithm { + return DigestAlgorithm{val} +} + +func StringDigestAlgorithm(val string) DigestAlgorithm { + return DigestAlgorithm{val} +} + +func DigestAlgorithmFromString(val string) DigestAlgorithm { + i, ok := stringToAlg[val] + if ok { + return DigestAlgorithm{i} + } + + i, err := strconv.Atoi(val) + if err == nil { + return DigestAlgorithm{i} + } + + return DigestAlgorithm{val} +} + +func DigestAlgorithmFromAny(val any) (DigestAlgorithm, error) { + switch t := val.(type) { + case int: + return IntDigestAlgorithm(t), nil + case int64: + return IntDigestAlgorithm(int(t)), nil + case float64: + return IntDigestAlgorithm(int(t)), nil + case string: + return DigestAlgorithmFromString(t), nil + default: + return DigestAlgorithm{0}, fmt.Errorf("invalid digest algorithm: %v(%T)", t, t) + } +} + +type DigestAlgorithm struct { + val any +} + +func (o DigestAlgorithm) IsString() bool { + _, ok := o.val.(string) + return ok +} + +func (o DigestAlgorithm) IsInt() bool { + _, ok := o.val.(int) + return ok +} + +func (o DigestAlgorithm) String() string { + switch t := o.val.(type) { + case string: + return t + case int: + text, ok := algToString[t] + if !ok { + text = fmt.Sprintf("%d", t) + } + + return text + default: + return "" + } +} + +func (o DigestAlgorithm) Int() int { + switch t := o.val.(type) { + case int: + return t + case string: + val := stringToAlg[t] + return val + default: + return 0 + } +} + +func (o DigestAlgorithm) MarshalCBOR() ([]byte, error) { + return em.Marshal(o.val) +} + +func (o *DigestAlgorithm) UnmarshalCBOR(data []byte) error { + if len(data) == 0 { + return errors.New("buffer too short") + } + + majorType := (data[0] & 0xe0) >> 5 + switch majorType { + case 0, 1: + var val int + if err := dm.Unmarshal(data, &val); err != nil { + return err + } + + o.val = val + return nil + case 3: + var val string + if err := dm.Unmarshal(data, &val); err != nil { + return err + } + + o.val = val + return nil + default: + return fmt.Errorf("unexpected CBOR major type for DigestAlgID: %d", majorType) + } +} + +func (o DigestAlgorithm) MarshalJSON() ([]byte, error) { + return json.Marshal(o.val) +} + +func (o *DigestAlgorithm) UnmarshalJSON(data []byte) error { + var val any + if err := json.Unmarshal(data, &val); err != nil { + return err + } + + switch t := val.(type) { + case float64: + *o = IntDigestAlgorithm(int(t)) + case string: + *o = StringDigestAlgorithm(t) + default: + return fmt.Errorf("unexpected algorithm value: %v(%T)", t, t) + } + + return nil +} + +func DigestFromString(val string) (Digest, error) { + parts := strings.Split(val, ";") + if len(parts) != 2 { + return Digest{}, fmt.Errorf("expected exactly two ;-separated parts, got %q", val) + } + + value, err := base64.RawURLEncoding.DecodeString(parts[1]) + if err != nil { + return Digest{}, fmt.Errorf("val: %w", err) + } + + return NewDigestStringAlg(parts[0], value), nil +} + +type Digest struct { + _ struct{} `cbor:",toarray"` + Algorithm DigestAlgorithm + Value []byte +} + +func NewDigestIntAlg(alg int, value []byte) Digest { + return NewDigest(IntDigestAlgorithm(alg), value) +} + +func NewDigestStringAlg(alg string, value []byte) Digest { + return NewDigest(DigestAlgorithmFromString(alg), value) +} + +func NewDigest(alg DigestAlgorithm, value []byte) Digest { + return Digest{Algorithm: alg, Value: value} +} + +func (o Digest) String() string { + return o.Algorithm.String() + ";" + base64.RawURLEncoding.EncodeToString(o.Value) +} + +func (o Digest) Valid() error { + if len(o.Value) == 0 { + return errors.New("zero length value") + } + + if o.Algorithm.Int() == 0 && !o.Algorithm.IsString() { + return errors.New("zero algorithm") + } + + wantLen, ok := algToValueLen[o.Algorithm.Int()] + if ok { + gotLen := len(o.Value) + if wantLen != gotLen { + return fmt.Errorf( + "length mismatch for hash algorithm %s: want %d bytes, got %d", + o.Algorithm.String(), wantLen, gotLen, + ) + } + } + + return nil +} + +func (o Digest) PublicKey() (crypto.PublicKey, error) { + return nil, errors.New("cannot get PublicKey from a digest") +} + +func (o Digest) Bytes() []byte { + ret, _ := em.Marshal(o) + return ret +} + +func (o Digest) MarshalJSON() ([]byte, error) { + toMarshal := [2]any{ + o.Algorithm, + base64.RawURLEncoding.EncodeToString(o.Value), + } + + return json.Marshal(toMarshal) +} + +func (o *Digest) UnmarshalJSON(data []byte) error { + var decoded []any + if err := json.Unmarshal(data, &decoded); err != nil { + return err + } + + if len(decoded) != 2 { + return fmt.Errorf("expected array with two elements, got %v", decoded) + } + + alg, err := DigestAlgorithmFromAny(decoded[0]) + if err != nil { + return fmt.Errorf("alg: %w", err) + } + + switch t := decoded[1].(type) { + case string: + bytes, err := base64.RawURLEncoding.DecodeString(t) + if err != nil { + return fmt.Errorf("val: %w", err) + } + + o.Algorithm = alg + o.Value = bytes + + return nil + default: + return fmt.Errorf("invalid val: %v(%T)", t, t) + } + +} diff --git a/comid/digest_test.go b/comid/digest_test.go new file mode 100644 index 00000000..13cbf207 --- /dev/null +++ b/comid/digest_test.go @@ -0,0 +1,276 @@ +// Copyright 2026 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package comid + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestDigestAlgorithm_conversion(t *testing.T) { + alg := IntDigestAlgorithm(Sha256) + assert.True(t, alg.IsInt()) + assert.False(t, alg.IsString()) + assert.Equal(t, Sha256, alg.Int()) + assert.Equal(t, "sha-256", alg.String()) + + alg = StringDigestAlgorithm("foo") + assert.False(t, alg.IsInt()) + assert.True(t, alg.IsString()) + assert.Equal(t, 0, alg.Int()) + assert.Equal(t, "foo", alg.String()) +} + +func TestDigestAlgorithmFromString(t *testing.T) { + testCases := []struct { + title string + text string + expected DigestAlgorithm + }{ + { + title: "int", + text: "-1", + expected: DigestAlgorithm{-1}, + }, + { + title: "known string", + text: "sha-256", + expected: DigestAlgorithm{Sha256}, + }, + { + title: "unknown string", + text: "foo", + expected: DigestAlgorithm{"foo"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + alg := DigestAlgorithmFromString(tc.text) + assert.EqualValues(t, tc.expected, alg) + }) + } +} + +func TestDigestAlgorithmFromAny(t *testing.T) { + testCases := []struct { + title string + value any + expected DigestAlgorithm + err string + }{ + { + title: "int", + value: Sha256, + expected: DigestAlgorithm{Sha256}, + }, + { + title: "int64", + value: int64(-1), + expected: DigestAlgorithm{-1}, + }, + { + title: "float64", + value: -1.0, + expected: DigestAlgorithm{-1}, + }, + { + title: "string", + value: "foo", + expected: DigestAlgorithm{"foo"}, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + alg, err := DigestAlgorithmFromAny(tc.value) + if tc.err == "" { + assert.NoError(t, err) + assert.EqualValues(t, tc.expected, alg) + } else { + assert.ErrorContains(t, err, tc.err) + } + }) + } +} + +func TestDigestAlgorithm_round_trip(t *testing.T) { + testCases := []struct { + title string + value DigestAlgorithm + expectedCBOR []byte + expectedJSON string + }{ + { + title: "known int", + value: IntDigestAlgorithm(Sha256), + expectedCBOR: []byte{0x01}, + expectedJSON: `1`, + }, + { + title: "nagative int", + value: IntDigestAlgorithm(-1), + expectedCBOR: []byte{0x20}, + expectedJSON: `-1`, + }, + { + title: "string", + value: StringDigestAlgorithm("foo"), + expectedCBOR: []byte{ + 0x63, // tstr(3) + 0x66, 0x6f, 0x6f, // . "foo" + }, + expectedJSON: `"foo"`, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + bytes, err := em.Marshal(tc.value) + assert.NoError(t, err) + assert.Equal(t, tc.expectedCBOR, bytes) + + var alg DigestAlgorithm + err = dm.Unmarshal(bytes, &alg) + assert.NoError(t, err) + assert.EqualValues(t, tc.value, alg) + + bytes, err = json.Marshal(tc.value) + assert.NoError(t, err) + assert.JSONEq(t, tc.expectedJSON, string(bytes)) + + err = json.Unmarshal(bytes, &alg) + assert.NoError(t, err) + assert.EqualValues(t, tc.value, alg) + }) + } + +} + +func TestDigestAlgorithm_UnmarshalCBOR_bad(t *testing.T) { + var alg DigestAlgorithm + err := alg.UnmarshalCBOR([]byte{}) + assert.ErrorContains(t, err, "buffer too short") + + err = alg.UnmarshalCBOR(MustHexDecode(t, "19")) + assert.ErrorContains(t, err, "unexpected EOF") + + err = alg.UnmarshalCBOR(MustHexDecode(t, "64ffffffff")) + assert.ErrorContains(t, err, "invalid UTF-8 string") + + err = alg.UnmarshalCBOR(MustHexDecode(t, "f4")) + assert.ErrorContains(t, err, "unexpected CBOR major type") +} + +func TestDigestAlgorithm_UnmarshalJSON_bad(t *testing.T) { + var alg DigestAlgorithm + err := alg.UnmarshalJSON([]byte{}) + assert.ErrorContains(t, err, "unexpected end of JSON input") + + err = alg.UnmarshalJSON([]byte("true")) + assert.ErrorContains(t, err, "unexpected algorithm value: true(bool)") +} + +func TestDigestFromString(t *testing.T) { + digest, err := DigestFromString("sha-256;AQID") + assert.NoError(t, err) + assert.EqualValues(t, NewDigestIntAlg(Sha256, []byte{0x01, 0x02, 0x03}), digest) + + _, err = DigestFromString("foo") + assert.ErrorContains(t, err, `expected exactly two ;-separated parts, got "foo"`) + + _, err = DigestFromString("foo;@@@") + assert.ErrorContains(t, err, "val: illegal base64 data") +} + +func TestDigest_Valid(t *testing.T) { + err := NewDigestIntAlg(Sha256, []byte{}).Valid() + assert.ErrorContains(t, err, "zero length value") + + err = NewDigestIntAlg(0, []byte{0x1, 0x2, 0x3}).Valid() + assert.ErrorContains(t, err, "zero algorithm") + + err = NewDigestIntAlg(Sha256, []byte{0x1, 0x2, 0x3}).Valid() + assert.ErrorContains(t, err, "length mismatch for hash algorithm sha-256") + + err = NewDigestStringAlg("foo", []byte{0x1, 0x2, 0x3}).Valid() + assert.NoError(t, err) +} + +func TestDigest_round_trip(t *testing.T) { + bytes := MustHexDecode(t, "deadbeef") + testCases := []struct { + title string + value Digest + expectedCBOR []byte + expectedJSON string + }{ + { + title: "int", + value: NewDigestIntAlg(Sha256, bytes), + expectedCBOR: []byte{ + 0x82, // array(2) + 0x01, // . [0]1 [sha-256] + 0x44, // . [1]bstr(4) + 0xde, 0xad, 0xbe, 0xef, + }, + expectedJSON: `[1, "3q2-7w"]`, + }, + { + title: "string", + value: NewDigestStringAlg("foo", bytes), + expectedCBOR: []byte{ + 0x82, // array(2) + 0x63, // . [0]tstr(3) + 0x66, 0x6f, 0x6f, // . . "foo" + 0x44, // . [1]bstr(4) + 0xde, 0xad, 0xbe, 0xef, + }, + expectedJSON: `["foo", "3q2-7w"]`, + }, + } + + for _, tc := range testCases { + t.Run(tc.title, func(t *testing.T) { + bytes, err := em.Marshal(tc.value) + assert.NoError(t, err) + assert.Equal(t, tc.expectedCBOR, bytes) + + var digest Digest + err = dm.Unmarshal(bytes, &digest) + assert.NoError(t, err) + assert.EqualValues(t, tc.value, digest) + + bytes, err = json.Marshal(tc.value) + assert.NoError(t, err) + assert.JSONEq(t, tc.expectedJSON, string(bytes)) + + err = json.Unmarshal(bytes, &digest) + assert.NoError(t, err) + assert.EqualValues(t, tc.value, digest) + }) + } + +} + +func TestDigest_UnmarshalJSON_bad(t *testing.T) { + var digest Digest + + err := digest.UnmarshalJSON([]byte("")) + assert.ErrorContains(t, err, "unexpected end of JSON input") + + err = digest.UnmarshalJSON([]byte(`{"alg": 1, "value": "foo"}`)) + assert.ErrorContains(t, err, "cannot unmarshal object into Go value of type []interface {}") + + err = digest.UnmarshalJSON([]byte("[1, 2, 3]")) + assert.ErrorContains(t, err, "expected array with two elements") + + err = digest.UnmarshalJSON([]byte(`[true, "bar"]`)) + assert.ErrorContains(t, err, "invalid digest algorithm: true(bool)") + + err = digest.UnmarshalJSON([]byte(`[1, "@@@"]`)) + assert.ErrorContains(t, err, "val: illegal base64 data") +} diff --git a/comid/digests.go b/comid/digests.go index d2a68445..026e88cf 100644 --- a/comid/digests.go +++ b/comid/digests.go @@ -1,4 +1,4 @@ -// Copyright 2021-2025 Contributors to the Veraison project. +// Copyright 2021-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -6,12 +6,10 @@ package comid import ( "bytes" "fmt" - - "github.com/veraison/swid" ) -// Digests is an alias for an array of SWID HashEntry -type Digests []swid.HashEntry +// Digests is an alias for an array of Digest +type Digests []Digest // NewDigests instantiates an empty array of Digests func NewDigests() *Digests { @@ -22,16 +20,16 @@ func NewDigests() *Digests { // the (already instantiated) Digests target. The method is a no-op if it is // invoked on a nil target and will refuse to add inconsistent algo/value // combinations. -func (o *Digests) AddDigest(algID uint64, value []byte) *Digests { +func (o *Digests) AddDigest(algID int, value []byte) *Digests { if o != nil { - *o = append(*o, swid.HashEntry{HashAlgID: algID, HashValue: value}) + *o = append(*o, NewDigestIntAlg(algID, value)) } return o } func (o Digests) Valid() error { for i, m := range o { - if err := swid.ValidHashEntry(m.HashAlgID, m.HashValue); err != nil { + if err := m.Valid(); err != nil { return fmt.Errorf("digest at index %d: %w", i, err) } } @@ -45,25 +43,25 @@ func (o Digests) Valid() error { // - All the elements that use the same algorithm have the same value, // though the elements could be in any order func (o Digests) Equal(r Digests) bool { - om := make(map[uint64][][]byte) + om := make(map[DigestAlgorithm][][]byte) for _, oe := range o { - vs, ok := om[oe.HashAlgID] + vs, ok := om[oe.Algorithm] if ok { - om[oe.HashAlgID] = append(vs, oe.HashValue) + om[oe.Algorithm] = append(vs, oe.Value) } else { - om[oe.HashAlgID] = [][]byte{oe.HashValue} + om[oe.Algorithm] = [][]byte{oe.Value} } } outer: for _, re := range r { - ovs, ok := om[re.HashAlgID] + ovs, ok := om[re.Algorithm] if !ok { return false } for _, ov := range ovs { - if bytes.Equal(ov, re.HashValue) { + if bytes.Equal(ov, re.Value) { continue outer } } @@ -87,26 +85,26 @@ func (o Digests) CompareAgainstReference(r Digests) bool { } // Insert the reference values into a map - ref := make(map[uint64][]byte) + ref := make(map[DigestAlgorithm][]byte) for _, digest := range r { - val, ok := ref[digest.HashAlgID] - if ok && !bytes.Equal(digest.HashValue, val) { + val, ok := ref[digest.Algorithm] + if ok && !bytes.Equal(digest.Value, val) { // If two entries with the same hashing algorithm have different // values, that's an automatic false. return false } - ref[digest.HashAlgID] = digest.HashValue + ref[digest.Algorithm] = digest.Value } // Check the object against the reference value map for _, digest := range o { - val, ok := ref[digest.HashAlgID] + val, ok := ref[digest.Algorithm] if !ok { continue } - if !bytes.Equal(digest.HashValue, val) { + if !bytes.Equal(digest.Value, val) { // All hash values must be equal if a claim has the same // digest represented using multiple algorithms. return false @@ -117,14 +115,3 @@ func (o Digests) CompareAgainstReference(r Digests) bool { return result } - -func NewHashEntry(algID uint64, value []byte) *swid.HashEntry { - var he swid.HashEntry - - err := he.Set(algID, value) - if err != nil { - return nil - } - - return &he -} diff --git a/comid/digests_test.go b/comid/digests_test.go index 0e610e87..8c9723cb 100644 --- a/comid/digests_test.go +++ b/comid/digests_test.go @@ -10,7 +10,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/swid" ) func TestDigests_AddDigest_OK(t *testing.T) { @@ -18,62 +17,64 @@ func TestDigests_AddDigest_OK(t *testing.T) { require.NotNil(t, d) tvs := []struct { - alg uint64 + alg int val []byte }{ { - alg: swid.Sha256, + alg: Sha256, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75"), }, { - alg: swid.Sha256_128, + alg: Sha256_128, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f36"), }, { - alg: swid.Sha256_120, + alg: Sha256_120, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f"), }, { - alg: swid.Sha256_96, + alg: Sha256_96, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3a"), }, { - alg: swid.Sha256_64, + alg: Sha256_64, val: MustHexDecode(t, "e45b72f5c0c0b572"), }, { - alg: swid.Sha256_32, + alg: Sha256_32, val: MustHexDecode(t, "e45b72ab"), }, { - alg: swid.Sha384, + alg: Sha384, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36"), }, { - alg: swid.Sha512, + alg: Sha512, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75"), }, { - alg: swid.Sha3_224, + alg: Sha3_224, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f36e45b72f5c0c0b572db4d8d3a"), }, { - alg: swid.Sha3_256, + alg: Sha3_256, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75"), }, { - alg: swid.Sha3_384, + alg: Sha3_384, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36"), }, { - alg: swid.Sha3_512, + alg: Sha3_512, val: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75"), }, } for _, tv := range tvs { - assert.NotNil(t, d.AddDigest(tv.alg, tv.val)) - assert.Nil(t, d.Valid()) + t.Run(IntDigestAlgorithm(tv.alg).String(), func(t *testing.T) { + assert.NotNil(t, d.AddDigest(tv.alg, tv.val)) + assert.Nil(t, d.Valid()) + }) } } func TestDigests_Valid_empty(t *testing.T) { @@ -81,12 +82,8 @@ func TestDigests_Valid_empty(t *testing.T) { require.NotNil(t, d) // simulate evil CBOR - *d = append(*d, swid.HashEntry{ - HashAlgID: 666, - HashValue: []byte{0x66, 0x66, 0x06}, - }) - - assert.EqualError(t, d.Valid(), "digest at index 0: unknown hash algorithm 666") + *d = append(*d, NewDigestIntAlg(0, []byte{0x66, 0x66, 0x06})) + assert.EqualError(t, d.Valid(), "digest at index 0: zero algorithm") } func TestDigests_AddDigest_unknown_algo(t *testing.T) { @@ -96,14 +93,14 @@ func TestDigests_AddDigest_unknown_algo(t *testing.T) { assert.NotNil(t, d.AddDigest(0, []byte("0 is a reserved value"))) err := d.Valid() - assert.ErrorContains(t, err, "digest at index 0: unknown hash algorithm") + assert.ErrorContains(t, err, "digest at index 0: zero algorithm") } func TestDigests_AddDigest_inconsistent_length_for_algo(t *testing.T) { d := NewDigests() require.NotNil(t, d) - assert.NotNil(t, d.AddDigest(swid.Sha3_512, MustHexDecode(t, "deadbeef"))) + assert.NotNil(t, d.AddDigest(Sha3_512, MustHexDecode(t, "deadbeef"))) err := d.Valid() assert.ErrorContains(t, err, "digest at index 0: length mismatch") @@ -111,11 +108,11 @@ func TestDigests_AddDigest_inconsistent_length_for_algo(t *testing.T) { func TestDigests_MarshalJSON(t *testing.T) { d := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) require.NotNil(t, d) - expected := `[ "sha-256-32;5Ftyqw==", "sha-256-64;5Fty9cDAtXI=" ]` + expected := `[ [6, "5Ftyqw"], [5, "5Fty9cDAtXI"] ]` actual, err := json.Marshal(d) @@ -125,8 +122,8 @@ func TestDigests_MarshalJSON(t *testing.T) { func TestDigests_MarshalCBOR(t *testing.T) { d := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) require.NotNil(t, d) // [[6, h'E45B72AB'], [5, h'E45B72F5C0C0B572']] @@ -149,100 +146,90 @@ func TestDigests_UnmarshalCBOR(t *testing.T) { err := dm.Unmarshal(tv, &actual) assert.Nil(t, err) - assert.Equal(t, swid.Sha256_32, actual[0].HashAlgID) - assert.Equal(t, MustHexDecode(t, "e45b72ab"), actual[0].HashValue) - assert.Equal(t, swid.Sha256_64, actual[1].HashAlgID) - assert.Equal(t, MustHexDecode(t, "e45b72f5c0c0b572"), actual[1].HashValue) + assert.Equal(t, Sha256_32, actual[0].Algorithm.Int()) + assert.Equal(t, MustHexDecode(t, "e45b72ab"), actual[0].Value) + assert.Equal(t, Sha256_64, actual[1].Algorithm.Int()) + assert.Equal(t, MustHexDecode(t, "e45b72f5c0c0b572"), actual[1].Value) } func TestDigests_Equal_True(t *testing.T) { ref := NewDigests(). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) claim := NewDigests(). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")) + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")). + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")) assert.True(t, claim.Equal(*ref)) } func TestDigests_Equal_False_Length(t *testing.T) { ref := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) claim := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")) assert.False(t, claim.Equal(*ref)) } func TestDigests_Equal_False_Mismatch(t *testing.T) { ref := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) claim := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "a26c83e2d0c0b572")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "a26c83e2d0c0b572")) assert.False(t, claim.Equal(*ref)) } func TestDigests_Compare_True(t *testing.T) { ref := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha384, MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha384, MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) claim := NewDigests(). - AddDigest(swid.Sha384, MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) + AddDigest(Sha384, MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) assert.True(t, claim.CompareAgainstReference(*ref)) } func TestDigests_Compare_False(t *testing.T) { ref := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) claim := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "f39a61fe")) + AddDigest(Sha256_32, MustHexDecode(t, "f39a61fe")) assert.False(t, claim.CompareAgainstReference(*ref)) } func TestDigests_Compare_False_DuplicateIDs(t *testing.T) { ref := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_32, MustHexDecode(t, "f34a51de")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_32, MustHexDecode(t, "f34a51de")) claim := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")) assert.False(t, claim.CompareAgainstReference(*ref)) } func TestDigests_Compare_False_PartialMatch(t *testing.T) { ref := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "e45b72f5c0c0b572")) claim := NewDigests(). - AddDigest(swid.Sha256_32, MustHexDecode(t, "e45b72ab")). - AddDigest(swid.Sha256_64, MustHexDecode(t, "f39c2473a0c0f592")) + AddDigest(Sha256_32, MustHexDecode(t, "e45b72ab")). + AddDigest(Sha256_64, MustHexDecode(t, "f39c2473a0c0f592")) assert.False(t, claim.CompareAgainstReference(*ref)) } - -func TestNewHashEntry(t *testing.T) { - // Valid hash entry - he := NewHashEntry(swid.Sha256, MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) - assert.NotNil(t, he) - - // Invalid hash entry - wrong length for algorithm - he = NewHashEntry(swid.Sha256, []byte{0x01, 0x02}) - assert.Nil(t, he) -} diff --git a/comid/example_test.go b/comid/example_test.go index 7b5e8961..e7d6c2f3 100644 --- a/comid/example_test.go +++ b/comid/example_test.go @@ -9,7 +9,6 @@ import ( "testing" "github.com/google/uuid" - "github.com/veraison/swid" ) func Example_encode() { @@ -36,8 +35,8 @@ func Example_encode() { MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + AddDigest(Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). SetFlagsTrue(FlagIsDebug). SetFlagsFalse(FlagIsSecure). SetSerialNumber("C02X70VHJHD5"). @@ -64,8 +63,8 @@ func Example_encode() { MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetMinSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + AddDigest(Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). SetFlagsTrue(FlagIsDebug). SetFlagsFalse(FlagIsSecure, FlagIsConfigured). SetSerialNumber("C02X70VHJHD5"). @@ -114,8 +113,8 @@ func Example_encode() { MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + AddDigest(Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). SetFlagsTrue(FlagIsDebug). SetFlagsFalse(FlagIsSecure). SetSerialNumber("C02X70VHJHD5"). @@ -133,8 +132,8 @@ func Example_encode() { MustNewUUIDMeasurement(TestUUID). SetRawValueBytes([]byte{0x01, 0x02, 0x03, 0x04}, []byte{0xff, 0xff, 0xff, 0xff}). SetSVN(2). - AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). - AddDigest(swid.Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). + AddDigest(Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}). + AddDigest(Sha256_32, []byte{0xff, 0xff, 0xff, 0xff}). SetFlagsTrue(FlagIsDebug). SetFlagsFalse(FlagIsSecure), ), @@ -162,7 +161,7 @@ func Example_encode() { // Output: // a50065656e2d474201a10078206d792d6e733a61636d652d726f616472756e6e65722d737570706c656d656e740282a3006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c6502820100a20069454d4341204c74642e0281020382a200781a6d792d6e733a61636d652d726f616472756e6e65722d626173650100a20078196d792d6e733a61636d652d726f616472756e6e65722d6f6c64010104a5008182a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa018182a300a500d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90229020282820644abcdef00820644ffffffff03a300f401f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa028182a101d902264702deadbeefdead81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d038182a101d8255031fb5abf023e4992aa4e95f9c1503bfa81d9022a78b12d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a304441516344516741455731427671462b2f727938425761375a454d553178595948455138420a6c4c54344d46484f614f2b4943547449767245654570722f7366544150363648326843486462354845584b74524b6f6436514c634f4c504131513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d08818282a300a500d86f445502c000016941434d45204c74642e026a526f616452756e6e65720300040101d902264702deadbeefdead02d8255031fb5abf023e4992aa4e95f9c1503bfa81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01aa01d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff064802005e1000000001075020010db8000000000000000000000068086c43303258373056484a484435094702deadbeefdead0a5031fb5abf023e4992aa4e95f9c1503bfa818281a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a501d90228020282820644abcdef00820644ffffffff03a201f403f504d9023044010203040544ffffffff81a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a3064802005e1000000001075020010db8000000000000000000000068094702deadbeefdead - // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"conditional-endorsement-series":[{"condition":{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]},"series":[{"selection":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":["sha-256-32;q83vAA==","sha-256-32;/////w=="],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w=="}}],"addition":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","ueid":"At6tvu/erQ=="}}]}]}]}} + // {"lang":"en-GB","tag-identity":{"id":"my-ns:acme-roadrunner-supplement"},"entities":[{"name":"ACME Ltd.","regid":"https://acme.example","roles":["creator","tagCreator"]},{"name":"EMCA Ltd.","roles":["maintainer"]}],"linked-tags":[{"target":"my-ns:acme-roadrunner-base","rel":"supplements"},{"target":"my-ns:acme-roadrunner-old","rel":"replaces"}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":[[6,"q83vAA"],[6,"_____w"]],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"endorsed-values":[{"environment":{"class":{"id":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"min-value","value":2},"digests":[[6,"q83vAA"],[6,"_____w"]],"flags":{"is-configured":false,"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]}],"dev-identity-keys":[{"environment":{"instance":{"type":"ueid","value":"At6tvu/erQ=="}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"attester-verification-keys":[{"environment":{"instance":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"verification-keys":[{"type":"pkix-base64-key","value":"-----BEGIN PUBLIC KEY-----\nMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEW1BvqF+/ry8BWa7ZEMU1xYYHEQ8B\nlLT4MFHOaO+ICTtIvrEeEpr/sfTAP66H2hCHdb5HEXKtRKod6QLcOLPA1Q==\n-----END PUBLIC KEY-----"}]}],"conditional-endorsement-series":[{"condition":{"environment":{"class":{"id":{"type":"oid","value":"2.5.2.8192"},"vendor":"ACME Ltd.","model":"RoadRunner","layer":0,"index":1},"instance":{"type":"ueid","value":"At6tvu/erQ=="},"group":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}},"measurements":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":[[6,"q83vAA"],[6,"_____w"]],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w==","mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","serial-number":"C02X70VHJHD5","ueid":"At6tvu/erQ==","uuid":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"}}]},"series":[{"selection":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"svn":{"type":"exact-value","value":2},"digests":[[6,"q83vAA"],[6,"_____w"]],"flags":{"is-secure":false,"is-debug":true},"raw-value":{"type":"bytes","value":"AQIDBA=="},"raw-value-mask":"/////w=="}}],"addition":[{"key":{"type":"uuid","value":"31fb5abf-023e-4992-aa4e-95f9c1503bfa"},"value":{"mac-addr":"02:00:5e:10:00:00:00:01","ip-addr":"2001:db8::68","ueid":"At6tvu/erQ=="}}]}]}]}} } func Example_encode_dependency_triples() { @@ -281,7 +280,7 @@ func Example_decode_JSON() { { "value": { "digests": [ - "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] } } @@ -306,7 +305,7 @@ func Example_decode_JSON() { }, "value": { "digests": [ - "sha-256:5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ], "svn": { "type": "exact-value", @@ -349,8 +348,8 @@ func Example_decode_JSON() { "notSecure" ], "digests": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=", - "sha-384;S1bPoH+usqtX3pIeSpfWVRRLVGRw66qrb3HA21GN31tKX7KPsq0bSTQmRCTrHlqG" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"], + [7, "S1bPoH-usqtX3pIeSpfWVRRLVGRw66qrb3HA21GN31tKX7KPsq0bSTQmRCTrHlqG"] ], "version": { "scheme": "semaver", @@ -436,8 +435,8 @@ func Example_decode_JSON() { "value": 1 }, "digests": [ - "sha-256-32;q83vAA==", - "sha-256-32;/////w==" + [6, "q83vAA"], + [6, "_____w"] ], "flags": { "is-secure": false, diff --git a/comid/integrityregisters.go b/comid/integrityregisters.go index ae713b0e..0e34d8b1 100644 --- a/comid/integrityregisters.go +++ b/comid/integrityregisters.go @@ -7,8 +7,6 @@ import ( "fmt" "reflect" "strconv" - - "github.com/veraison/swid" ) const TextType = "text" @@ -42,7 +40,7 @@ func (i *IntegrityRegisters) AddDigests(index IRegisterIndex, digests Digests) e // AddDigest allows inserting a digest at a specific index // Supported index types are uint and text -func (i *IntegrityRegisters) AddDigest(index IRegisterIndex, digest swid.HashEntry) error { +func (i *IntegrityRegisters) AddDigest(index IRegisterIndex, digest Digest) error { if i.IndexMap == nil { return fmt.Errorf("no register to add digest") } diff --git a/comid/integrityregisters_test.go b/comid/integrityregisters_test.go index 204651d5..c83e5c08 100644 --- a/comid/integrityregisters_test.go +++ b/comid/integrityregisters_test.go @@ -1,4 +1,4 @@ -// Copyright 2024-2025 Contributors to the Veraison project. +// Copyright 2024-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid @@ -10,13 +10,12 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/swid" ) func prepareRegister(t *testing.T, iType string) (*IntegrityRegisters, error) { var err error val := MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75") - entry := swid.HashEntry{HashAlgID: swid.Sha256, HashValue: val} + entry := NewDigestIntAlg(Sha256, val) reg := NewIntegrityRegisters() for index := 0; index < 5; index++ { switch iType { @@ -45,20 +44,20 @@ func TestIntegrityRegisters_AddDigest_OK(t *testing.T) { func TestIntegrityRegisters_AddDigest_NOK(t *testing.T) { expectedErr := `no register to add digest` register := IntegrityRegisters{} - err := register.AddDigest(uint(0), swid.HashEntry{}) + err := register.AddDigest(uint(0), Digest{}) assert.EqualError(t, err, expectedErr) expectedErr = `unexpected type for index: bool` var k bool reg, err := prepareRegister(t, "uint") require.NoError(t, err) - err = reg.AddDigest(k, swid.HashEntry{}) + err = reg.AddDigest(k, Digest{}) assert.EqualError(t, err, expectedErr) } func TestIntegrityRegisters_AddDigests_NOK(t *testing.T) { expectedErr := `no digests to add` register := IntegrityRegisters{} - err := register.AddDigests(uint(0), []swid.HashEntry{}) + err := register.AddDigests(uint(0), []Digest{}) assert.EqualError(t, err, expectedErr) } @@ -82,11 +81,11 @@ func TestIntegrityRegisters_MarshalCBOR_UIntIndex_OK(t *testing.T) { func TestIntegrityRegisters_UnmarshalCBOR_UIntIndex_OK(t *testing.T) { expected := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} bstr := MustHexDecode(nil, `a5008182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75018182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75028182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75038182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75048182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75`) actual := IntegrityRegisters{} @@ -100,31 +99,31 @@ func TestIntegrityRegisters_MarshalJSON_UIntIndex_OK(t *testing.T) { "0": { "key-type": "uint", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "1": { "key-type": "uint", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "2": { "key-type": "uint", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "3": { "key-type": "uint", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "4": { "key-type": "uint", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] } } @@ -157,11 +156,11 @@ func TestIntegrityRegisters_MarshalCBOR_TextIndex_OK(t *testing.T) { func TestIntegrityRegisters_UnmarshalCBOR_TextIndex_OK(t *testing.T) { expected := IntegrityRegisters{map[IRegisterIndex]Digests{ - "0": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - "1": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - "2": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - "3": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - "4": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "0": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "1": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "2": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "3": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "4": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} bstr := MustHexDecode(t, `a561308182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561318182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561328182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561338182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7561348182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75`) actual := IntegrityRegisters{} @@ -175,31 +174,31 @@ func TestIntegrityRegisters_MarshalJSON_TextIndex_OK(t *testing.T) { "0": { "key-type": "text", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "1": { "key-type": "text", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "2": { "key-type": "text", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "3": { "key-type": "text", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] }, "4": { "key-type": "text", "value": [ - "sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=" + [1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"] ] } } @@ -213,9 +212,9 @@ func TestIntegrityRegisters_MarshalJSON_TextIndex_OK(t *testing.T) { } func TestIntegrityRegisters_UnmarshalJSON_TextIndex_OK(t *testing.T) { - j := `{"abcd":{"key-type":"text","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}` + j := `{"abcd":{"key-type":"text","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}}` expected := IntegrityRegisters{map[IRegisterIndex]Digests{ - "abcd": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "abcd": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} actual := IntegrityRegisters{} err := actual.UnmarshalJSON([]byte(j)) @@ -225,18 +224,18 @@ func TestIntegrityRegisters_UnmarshalJSON_TextIndex_OK(t *testing.T) { func TestIntegrityRegisters_UnmarshalJSON_UIntIndex_OK(t *testing.T) { j := `{ - "0":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "2":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "3":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "4":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} + "0":{"key-type":"uint","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "1":{"key-type":"uint","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "2":{"key-type":"uint","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "3":{"key-type":"uint","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "4":{"key-type":"uint","value":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]} }` expected := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} actual := IntegrityRegisters{} err := actual.UnmarshalJSON([]byte(j)) @@ -246,19 +245,19 @@ func TestIntegrityRegisters_UnmarshalJSON_UIntIndex_OK(t *testing.T) { func TestIntegrityRegisters_UnmarshalJSON_TextUInt_Index_OK(t *testing.T) { j := `{ - "0":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "2":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "3":{"key-type":"text","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}, - "4":{"key-type":"text","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]} + "0":{"key-type":"uint","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "1":{"key-type":"uint","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "2":{"key-type":"uint","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "3":{"key-type":"text","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}, + "4":{"key-type":"text","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]} }` expected := IntegrityRegisters{ map[IRegisterIndex]Digests{ - uint(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - "3": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - "4": []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "3": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + "4": []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} actual := IntegrityRegisters{} err := actual.UnmarshalJSON([]byte(j)) @@ -274,48 +273,48 @@ func TestIntegrityRegisters_UnmarshalJSON_NOK(t *testing.T) { }{ { Name: "invalid input integer", - Input: `{"0":{"key-type":"int","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Input: `{"0":{"key-type":"int","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}}`, Err: "unexpected key type for index: int", }, { Name: "negative index", - Input: `{"-1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Input: `{"-1":{"key-type":"uint","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}}`, Err: `invalid negative integer key`, }, { Name: "not an integer", - Input: `{"0.2345":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}}`, + Input: `{"0.2345":{"key-type":"uint","value":[[1, "5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}}`, Err: `unable to convert key to uint: strconv.Atoi: parsing "0.2345": invalid syntax`, }, { Name: "invalid digest", - Input: `{"1":{"key-type":"uint","value":["sha-256;5Fty9cDAtXLbTY06t+l/3TmI0eoJN7LZ6hOUiTXU="]}}`, - Err: `unable to unmarshal Digests: illegal base64 data at input byte 40`, + Input: `{"1":{"key-type":"uint","value":[[1, "5Fty9cDAtXLbTY06t-l_3TmI0eoJN7LZ6hOUiTX@"]]}}`, + Err: `unable to unmarshal Digests: val: illegal base64 data`, }, } { t.Run(tv.Name, func(t *testing.T) { reg := IntegrityRegisters{} err := reg.UnmarshalJSON([]byte(tv.Input)) - assert.EqualError(t, err, tv.Err) + assert.ErrorContains(t, err, tv.Err) }) } } func TestIntegrityRegisters_Equal_True(t *testing.T) { claim := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} ref := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} assert.True(t, claim.Equal(ref)) @@ -323,18 +322,18 @@ func TestIntegrityRegisters_Equal_True(t *testing.T) { func TestIntegrityRegisters_Equal_False(t *testing.T) { claim := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} ref := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} assert.False(t, claim.Equal(ref)) @@ -342,16 +341,16 @@ func TestIntegrityRegisters_Equal_False(t *testing.T) { func TestIntegrityRegisters_Compare_True(t *testing.T) { claim := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "9aca8354b65a9b4815cf471a6fe9ca9629389691c4183831e63c37a744b2d8ec")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "9aca8354b65a9b4815cf471a6fe9ca9629389691c4183831e63c37a744b2d8ec")}}, }} ref := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, }} assert.True(t, claim.CompareAgainstReference(ref)) @@ -359,15 +358,15 @@ func TestIntegrityRegisters_Compare_True(t *testing.T) { func TestIntegrityRegisters_Compare_False(t *testing.T) { claim := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "9aca8354b65a9b4815cf471a6fe9ca9629389691c4183831e63c37a744b2d8ec")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "9aca8354b65a9b4815cf471a6fe9ca9629389691c4183831e63c37a744b2d8ec")}}, }} ref := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} assert.False(t, claim.CompareAgainstReference(ref)) @@ -375,14 +374,14 @@ func TestIntegrityRegisters_Compare_False(t *testing.T) { func TestIntegrityRegisters_Compare_False_MissingEntry(t *testing.T) { claim := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(0): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, - uint64(1): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, - uint64(2): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, - uint64(3): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, + uint64(0): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(1): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "34b3bd704b13febb14eca0a3174114cea735e0c92e70c3d0f5cd78d653e5678b")}}, + uint64(2): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "58af0069d43712309b37d645e6729eca3e5aee9d22bdb595c31b59ee6e2d3750")}}, + uint64(3): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "408d1344f60ec4a06a610406c84cee1d9a5c524b0ddd1264719cc347f4b15a08")}}, }} ref := IntegrityRegisters{map[IRegisterIndex]Digests{ - uint64(4): []swid.HashEntry{{HashAlgID: swid.Sha256, HashValue: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, + uint64(4): []Digest{{Algorithm: IntDigestAlgorithm(Sha256), Value: MustHexDecode(t, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")}}, }} assert.False(t, claim.CompareAgainstReference(ref)) diff --git a/comid/measurement.go b/comid/measurement.go index 97a22b81..938f57ea 100644 --- a/comid/measurement.go +++ b/comid/measurement.go @@ -651,7 +651,7 @@ func (o *Measurement) SetMinSVN(svn uint64) *Measurement { // AddDigest add the supplied digest - comprising the digest itself together // with the hash algorithm used to obtain it - to the measurement-values-map of // the target measurement -func (o *Measurement) AddDigest(algID uint64, digest []byte) *Measurement { +func (o *Measurement) AddDigest(algID int, digest []byte) *Measurement { if o != nil { ds := o.Val.Digests if ds == nil { diff --git a/comid/measurement_test.go b/comid/measurement_test.go index df136c06..ebbf870e 100644 --- a/comid/measurement_test.go +++ b/comid/measurement_test.go @@ -64,7 +64,7 @@ func TestMeasurement_NewUUIDMeasurement_bad_digest(t *testing.T) { tv, err := NewUUIDMeasurement(TestUUID) require.NoError(t, err) - assert.NotNil(t, tv.AddDigest(swid.Sha256, []byte{0xff})) + assert.NotNil(t, tv.AddDigest(Sha256, []byte{0xff})) assert.ErrorContains(t, tv.Valid(), "digest at index 0") } @@ -569,7 +569,7 @@ func TestMval_Valid(t *testing.T) { t.Run("Digests invalid", func(t *testing.T) { ds := NewDigests() - ds.AddDigest(swid.Sha256, []byte{0xAA, 0xBB}) + ds.AddDigest(Sha256, []byte{0xAA, 0xBB}) mval := Mval{ Digests: ds, } diff --git a/comid/test_vars.go b/comid/test_vars.go index 2f5b96d5..4ad5c099 100644 --- a/comid/test_vars.go +++ b/comid/test_vars.go @@ -244,9 +244,9 @@ rpT6kiSnAzk+2zgSiA4= TestCOSEKeySetMulti = MustHexDecode(nil, `82a501020258246d65726961646f632e6272616e64796275636b406275636b6c616e642e6578616d706c65200121582065eda5a12577c2bae829437fe338701a10aaa375e1bb5b5de108de439c08551d2258201e52ed75701163f7f9e40ddf9f341b3dc9ba860af7e0ca7ca7e9eecd0084d19ca601010327048202647369676e0543030201200621582015522ef15729ccf39509ea5c15a26be949e38807a5c26ef9281487ef4ae67b46`) - TestThumbprint = swid.HashEntry{ - HashAlgID: 1, - HashValue: MustHexDecode(nil, `68e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728`), + TestThumbprint = Digest{ + Algorithm: IntDigestAlgorithm(Sha256), + Value: MustHexDecode(nil, `68e656b251e67e8358bef8483ab0d51c6619f3e7a1a9f0e75838d41ff368f728`), } TestTaggedBytes = []byte("taggedbytes") diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go index df06e7cd..08842d77 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -12,7 +12,6 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/extensions" - "github.com/veraison/swid" ) // ----- profile definition ----- @@ -102,7 +101,7 @@ func Example_profile_unmarshal() { for i := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { m := &extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values[i] - val := hex.EncodeToString((*m.Val.Digests)[0].HashValue) + val := hex.EncodeToString((*m.Val.Digests)[0].Value) tsInt := m.Val.MustGetInt64("timestamp") ts := time.Unix(tsInt, 0).UTC() @@ -159,7 +158,7 @@ func Example_profile_marshal() { if err != nil { log.Fatalf("could not create measurement: %v", err) } - measurement.AddDigest(swid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}) + measurement.AddDigest(comid.Sha256_32, []byte{0xab, 0xcd, 0xef, 0x00}) // alternatively, we can add extensions to individual value before // adding it to the collection. Note that because we're adding the diff --git a/corim/oneormore_test.go b/corim/oneormore_test.go index 10b922fd..947dce69 100644 --- a/corim/oneormore_test.go +++ b/corim/oneormore_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/veraison/corim/comid" - "github.com/veraison/swid" ) func Test_OneOrMore_serilize_round_trip(t *testing.T) { @@ -62,24 +61,24 @@ func Test_OneOrMore_serilize_round_trip(t *testing.T) { }) } - hash1 := *comid.NewHashEntry( - swid.Sha256, + hash1 := comid.NewDigestIntAlg( + comid.Sha256, comid.MustHexDecode(t, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), ) - hash2 := *comid.NewHashEntry( - swid.Sha256, + hash2 := comid.NewDigestIntAlg( + comid.Sha256, comid.MustHexDecode(t, "c0decafec0decafec0decafec0decafec0decafec0decafec0decafec0decafe"), ) digestCases := []struct { title string - oom OneOrMore[swid.HashEntry] + oom OneOrMore[comid.Digest] expectdCBOR []byte expectedJSON string }{ { title: "one digest", - oom: OneOrMore[swid.HashEntry]{hash1}, + oom: OneOrMore[comid.Digest]{hash1}, expectdCBOR: []byte{ 0x82, // array(2) 0x01, // . [0]1 [sha-256] @@ -89,11 +88,11 @@ func Test_OneOrMore_serilize_round_trip(t *testing.T) { 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, 0xde, 0xad, 0xbe, 0xef, // 32 }, - expectedJSON: `"sha-256;3q2+796tvu/erb7v3q2+796tvu/erb7v3q2+796tvu8="`, + expectedJSON: `[1, "3q2-796tvu_erb7v3q2-796tvu_erb7v3q2-796tvu8"]`, }, { title: "more digests", - oom: OneOrMore[swid.HashEntry]{hash1, hash2}, + oom: OneOrMore[comid.Digest]{hash1, hash2}, expectdCBOR: []byte{ 0x82, // array(2) 0x82, // . [0]array(2) @@ -111,7 +110,7 @@ func Test_OneOrMore_serilize_round_trip(t *testing.T) { 0xc0, 0xde, 0xca, 0xfe, 0xc0, 0xde, 0xca, 0xfe, 0xc0, 0xde, 0xca, 0xfe, 0xc0, 0xde, 0xca, 0xfe, // 32 }, - expectedJSON: `["sha-256;3q2+796tvu/erb7v3q2+796tvu/erb7v3q2+796tvu8=","sha-256;wN7K/sDeyv7A3sr+wN7K/sDeyv7A3sr+wN7K/sDeyv4="]`, + expectedJSON: `[[1, "3q2-796tvu_erb7v3q2-796tvu_erb7v3q2-796tvu8"],[1, "wN7K_sDeyv7A3sr-wN7K_sDeyv7A3sr-wN7K_sDeyv4"]]`, }, } @@ -121,16 +120,16 @@ func Test_OneOrMore_serilize_round_trip(t *testing.T) { assert.NoError(t, err) assert.Equal(t, tc.expectdCBOR, encoded) - var decoded OneOrMore[swid.HashEntry] + var decoded OneOrMore[comid.Digest] err = decoded.UnmarshalCBOR(encoded) assert.NoError(t, err) assert.Equal(t, tc.oom, decoded) encoded, err = tc.oom.MarshalJSON() assert.NoError(t, err) - assert.Equal(t, tc.expectedJSON, string(encoded)) + assert.JSONEq(t, tc.expectedJSON, string(encoded)) - decoded = OneOrMore[swid.HashEntry]{} + decoded = OneOrMore[comid.Digest]{} err = decoded.UnmarshalJSON(encoded) assert.NoError(t, err) assert.Equal(t, tc.oom, decoded) diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index e3ef7321..859624b8 100644 --- a/corim/unsignedcorim.go +++ b/corim/unsignedcorim.go @@ -153,14 +153,14 @@ func (o *UnsignedCorim) AddCoswid(c *swid.SoftwareIdentity) *UnsignedCorim { // AddDependentRim creates a corim-locator-map from the supplied arguments and // appends it to the dependent RIMs in the unsigned-corim-map -func (o *UnsignedCorim) AddDependentRim(href string, thumbprint *swid.HashEntry) *UnsignedCorim { +func (o *UnsignedCorim) AddDependentRim(href string, thumbprint *comid.Digest) *UnsignedCorim { if o != nil { l := Locator{ Href: OneOrMore[comid.TaggedURI]{comid.TaggedURI(href)}, } if thumbprint != nil { - l.Thumbprint = &OneOrMore[swid.HashEntry]{*thumbprint} + l.Thumbprint = &OneOrMore[comid.Digest]{*thumbprint} } if o.DependentRims == nil { @@ -573,7 +573,7 @@ func (o *Tag) UnmarshalJSON(data []byte) error { // JSON serialization. type Locator struct { Href OneOrMore[comid.TaggedURI] `cbor:"0,keyasint" json:"href"` - Thumbprint *OneOrMore[swid.HashEntry] `cbor:"1,keyasint,omitempty" json:"thumbprint,omitempty"` + Thumbprint *OneOrMore[comid.Digest] `cbor:"1,keyasint,omitempty" json:"thumbprint,omitempty"` } func (o Locator) Valid() error { @@ -590,7 +590,7 @@ func (o Locator) Valid() error { } for i, tp := range *o.Thumbprint { - if err := swid.ValidHashEntry(tp.HashAlgID, tp.HashValue); err != nil { + if err := tp.Valid(); err != nil { return fmt.Errorf("invalid locator thumbprint at index %d: %w", i, err) } } diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 10014199..5ea46d08 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -437,8 +437,8 @@ func TestLocator_Valid(t *testing.T) { l.Href = OneOrMore[comid.TaggedURI]{comid.TaggedURI("https://example.com")} assert.NoError(t, l.Valid()) - l.Thumbprint = &OneOrMore[swid.HashEntry]{swid.HashEntry{}} - assert.EqualError(t, l.Valid(), "invalid locator thumbprint at index 0: unknown hash algorithm 0") + l.Thumbprint = &OneOrMore[comid.Digest]{comid.Digest{}} + assert.EqualError(t, l.Valid(), "invalid locator thumbprint at index 0: zero length value") } diff --git a/coserv/coserv_test.go b/coserv/coserv_test.go index 0a035469..b3010ff5 100644 --- a/coserv/coserv_test.go +++ b/coserv/coserv_test.go @@ -11,7 +11,6 @@ import ( "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" "github.com/veraison/go-cose" - "github.com/veraison/swid" ) func TestCoserv_ToCBOR_rv_class_simple(t *testing.T) { @@ -282,15 +281,15 @@ func TestCoserv_FromCBOR_Results(t *testing.T) { assert.Len(t, *(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests, 2) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[0].HashAlgID, swid.Sha256) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[0].HashValue, []byte{0xaa}) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[1].HashAlgID, swid.Sha256_128) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[1].HashValue, []byte{0xbb}) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[0].Algorithm.Int(), comid.Sha256) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[0].Value, []byte{0xaa}) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[1].Algorithm.Int(), comid.Sha256_128) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[0].Val.Digests)[1].Value, []byte{0xbb}) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[0].HashAlgID, swid.Sha256) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[0].HashValue, []byte{0xcc}) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[1].HashAlgID, swid.Sha256_128) - assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[1].HashValue, []byte{0xdd}) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[0].Algorithm.Int(), comid.Sha256) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[0].Value, []byte{0xcc}) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[1].Algorithm.Int(), comid.Sha256_128) + assert.Equal(t, (*(*actual.Results.RVQ)[0].RVTriple.Measurements.Values[1].Val.Digests)[1].Value, []byte{0xdd}) } func TestCoserv_FromCBOR_Results_Source_Artifacts(t *testing.T) { diff --git a/profiles/cca/platform.go b/profiles/cca/platform.go index 83f90511..7ed8cf1e 100644 --- a/profiles/cca/platform.go +++ b/profiles/cca/platform.go @@ -163,7 +163,7 @@ func validateCCADigests(digests *comid.Digests) error { // The hash value size is what matters most for validation // Hash value must be 32, 48, or 64 bytes (SHA-256, SHA-384, SHA-512) - if err := ValidateHashDigestSize(digest.HashValue); err != nil { + if err := ValidateHashDigestSize(digest.Value); err != nil { return fmt.Errorf("digest at index %d: %w", i, err) } } diff --git a/profiles/cca/platform_test.go b/profiles/cca/platform_test.go index b55e311f..c5556253 100644 --- a/profiles/cca/platform_test.go +++ b/profiles/cca/platform_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" - "github.com/veraison/swid" ) // Helper Functions @@ -243,7 +242,7 @@ func TestValidateCCADigests_AllCases(t *testing.T) { title: "valid digest with SHA-256 (32 bytes)", digests: func() *comid.Digests { d := &comid.Digests{} - d.AddDigest(swid.Sha256, make([]byte, 32)) + d.AddDigest(comid.Sha256, make([]byte, 32)) return d }(), shouldError: false, @@ -252,7 +251,7 @@ func TestValidateCCADigests_AllCases(t *testing.T) { title: "valid digest with SHA-384 (48 bytes)", digests: func() *comid.Digests { d := &comid.Digests{} - d.AddDigest(swid.Sha384, make([]byte, 48)) + d.AddDigest(comid.Sha384, make([]byte, 48)) return d }(), shouldError: false, @@ -261,7 +260,7 @@ func TestValidateCCADigests_AllCases(t *testing.T) { title: "valid digest with SHA-512 (64 bytes)", digests: func() *comid.Digests { d := &comid.Digests{} - d.AddDigest(swid.Sha512, make([]byte, 64)) + d.AddDigest(comid.Sha512, make([]byte, 64)) return d }(), shouldError: false, @@ -472,7 +471,7 @@ func TestValidateCCAPlatformReferenceValue_AllCases(t *testing.T) { // Set digests digests := &comid.Digests{} hash := make([]byte, 32) - digests.AddDigest(swid.Sha256, hash) + digests.AddDigest(comid.Sha256, hash) measurement.Val.Digests = digests // Set signer-id (cryptokeys) diff --git a/profiles/cca/realm.go b/profiles/cca/realm.go index 55dabbd4..181d3304 100644 --- a/profiles/cca/realm.go +++ b/profiles/cca/realm.go @@ -172,7 +172,7 @@ func validateCCARealmMeasurement(measurement *comid.Measurement) error { digest := (*digests)[0] // Hash value must be 32, 48, or 64 bytes (SHA-256, SHA-384, SHA-512) - if err := ValidateHashDigestSize(digest.HashValue); err != nil { + if err := ValidateHashDigestSize(digest.Value); err != nil { return fmt.Errorf("hash value must be 32, 48, or 64 bytes") } diff --git a/profiles/cca/realm_test.go b/profiles/cca/realm_test.go index 64271d8f..0f40a008 100644 --- a/profiles/cca/realm_test.go +++ b/profiles/cca/realm_test.go @@ -9,7 +9,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" - "github.com/veraison/swid" ) // Helper Functions for Realm Tests @@ -48,7 +47,7 @@ func mustNewCCARealmDigestMeasurement(mkey string, hashSize int) *comid.Measurem // Set digests with specified hash size digests := &comid.Digests{} hash := make([]byte, hashSize) - digests.AddDigest(swid.Sha256, hash) + digests.AddDigest(comid.Sha256, hash) measurement.Val.Digests = digests return measurement @@ -211,8 +210,8 @@ func TestValidateCCARealmMeasurement_AllCases(t *testing.T) { // Add two digests digests := &comid.Digests{} - digests.AddDigest(swid.Sha256, make([]byte, 32)) - digests.AddDigest(swid.Sha384, make([]byte, 48)) + digests.AddDigest(comid.Sha256, make([]byte, 32)) + digests.AddDigest(comid.Sha384, make([]byte, 48)) measurement.Val.Digests = digests return measurement diff --git a/profiles/tdx/example_qe_refval_test.go b/profiles/tdx/example_qe_refval_test.go index c9cf43e8..32796ad5 100644 --- a/profiles/tdx/example_qe_refval_test.go +++ b/profiles/tdx/example_qe_refval_test.go @@ -115,7 +115,7 @@ func Example_encode_tdx_QE_refval_without_profile() { // output: // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02703031323334353637383941424344454681a101a53848d9ea6a82020a385046c000fbff00003853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f363854013855d9ea6a82020b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"miscselect":"wAD7/wAA","mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"uint","value":1},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"0123456789ABCDEF"}},"measurements":[{"value":{"isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"miscselect":"wAD7/wAA","mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]}},"isvprodid":{"type":"uint","value":1},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func Example_decode_QE_CBOR() { diff --git a/profiles/tdx/example_seam_refval_test.go b/profiles/tdx/example_seam_refval_test.go index 14547257..6a78caa1 100644 --- a/profiles/tdx/example_seam_refval_test.go +++ b/profiles/tdx/example_seam_refval_test.go @@ -102,7 +102,7 @@ func Example_encode_tdx_seam_refval_without_profile() { } // output: // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc803848d9ea6a82020a38514201013852d9ea7482068182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d753853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638544201013855d9ea6a82020b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func Example_encode_tdx_seam_refval_with_profile() { @@ -154,7 +154,7 @@ func Example_encode_tdx_seam_refval_with_profile() { // output: // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc803848d9ea6a82020a38514201013852d9ea7482068182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d753853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638544201013855d9ea6a82020b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func Example_encode_tdx_seam_refval_direct() { @@ -203,7 +203,7 @@ func Example_encode_tdx_seam_refval_direct() { // output: // a301a1005043bbe37f2e614b33aed353cff1428b200281a30065494e54454c01d8207168747470733a2f2f696e74656c2e636f6d028301000204a1008182a100a300d86f4c6086480186f84d01020304050171496e74656c20436f72706f726174696f6e02675444585345414d81a101a73847c11a6796cc803848d9ea6a82020a38514201013852d9ea7482068182015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d753853d9ea7482068282015820e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d7582075830e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f3638544201013855d9ea6a82020b - // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU="]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":["sha-256;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXU=","sha-384;5Fty9cDAtXLbTY06t+l/No/3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} + // {"tag-identity":{"id":"43bbe37f-2e61-4b33-aed3-53cff1428b20"},"entities":[{"name":"INTEL","regid":"https://intel.com","roles":["creator","tagCreator","maintainer"]}],"triples":{"reference-values":[{"environment":{"class":{"id":{"type":"oid","value":"2.16.840.1.113741.1.2.3.4.5"},"vendor":"Intel Corporation","model":"TDXSEAM"}},"measurements":[{"value":{"tcbdate":"2025-01-27T00:00:00Z","isvsvn":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":10}}},"attributes":"AQE=","mrtee":{"type":"digest-expression","value":{"set-operator":"member","set-digest":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"]]}},"mrsigner":{"type":"digest-expression","value":{"set-operator":"member","set-digest":[[1,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXU"],[7,"5Fty9cDAtXLbTY06t-l_No_3TmI0eoJN7LZ6hOUiTXXkW3L1wMC1cttNjTq36X82"]]}},"isvprodid":{"type":"bytes","value":"AQE="},"tcbevalnum":{"type":"numeric-expression","value":{"numeric-operator":"greater_or_equal","numeric-type":{"type":"uint","value":11}}}}}]}]}} } func Example_decode_CBOR() { diff --git a/profiles/tdx/teedigest_test.go b/profiles/tdx/teedigest_test.go index 499f1c19..1dc85900 100644 --- a/profiles/tdx/teedigest_test.go +++ b/profiles/tdx/teedigest_test.go @@ -9,12 +9,11 @@ import ( "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" "github.com/veraison/corim/comid" - "github.com/veraison/swid" ) func getNewDigests() Digests { d := comid.NewDigests() - d.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) + d.AddDigest(comid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) return *d } func TestTeeDigest_NewTeeDigest_OK(t *testing.T) { diff --git a/profiles/tdx/test_common_methods.go b/profiles/tdx/test_common_methods.go index f6f3de4e..149fe61e 100644 --- a/profiles/tdx/test_common_methods.go +++ b/profiles/tdx/test_common_methods.go @@ -1,4 +1,4 @@ -// Copyright 2025 Contributors to the Veraison project. +// Copyright 2025-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package tdx @@ -46,8 +46,8 @@ func extractDigest(typ string, d *Digests) error { } for _, digest := range *d { - fmt.Printf("\n%s Digest Alg: %d", typ, digest.HashAlgID) - fmt.Printf("\n%s Digest Value: %x", typ, digest.HashValue) + fmt.Printf("\n%s Digest Alg: %d", typ, digest.Algorithm.Int()) + fmt.Printf("\n%s Digest Value: %x", typ, digest.Value) } return nil diff --git a/profiles/tdx/test_qe_methods.go b/profiles/tdx/test_qe_methods.go index 730e8bb6..32140bf0 100644 --- a/profiles/tdx/test_qe_methods.go +++ b/profiles/tdx/test_qe_methods.go @@ -8,7 +8,6 @@ import ( "fmt" "github.com/veraison/corim/comid" - "github.com/veraison/swid" ) //nolint:funlen // reason: this function is long but readability is fine @@ -59,9 +58,9 @@ func SetTdxQeMvalExtensions(typ MessageType, val *comid.Mval) error { var ts *TeeDigest // assign mrSignerDigest dSign := comid.NewDigests() - dSign.AddDigest(swid.Sha256, + dSign.AddDigest(comid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) - dSign.AddDigest(swid.Sha384, + dSign.AddDigest(comid.Sha384, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) // assign mrsignerDigest diff --git a/profiles/tdx/test_seam_methods.go b/profiles/tdx/test_seam_methods.go index ce9ee41a..05a6563a 100644 --- a/profiles/tdx/test_seam_methods.go +++ b/profiles/tdx/test_seam_methods.go @@ -9,7 +9,6 @@ import ( "time" "github.com/veraison/corim/comid" - "github.com/veraison/swid" ) //nolint:funlen // reason: this function is long but readability is fine @@ -76,13 +75,13 @@ func SetTDXSeamMvalExtensions(typ MessageType, val *comid.Mval) error { var td, ts *TeeDigest // assign mrteeDigest dTee := comid.NewDigests() - dTee.AddDigest(swid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) + dTee.AddDigest(comid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) // assign mrSignerDigest dSign := comid.NewDigests() - dSign.AddDigest(swid.Sha256, + dSign.AddDigest(comid.Sha256, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75")) - dSign.AddDigest(swid.Sha384, + dSign.AddDigest(comid.Sha384, comid.MustHexDecode(nil, "e45b72f5c0c0b572db4d8d3ab7e97f368ff74e62347a824decb67a84e5224d75e45b72f5c0c0b572db4d8d3ab7e97f36")) // assign mrsignerDigest diff --git a/profiles/tdx/test_vars.go b/profiles/tdx/test_vars.go index 24d07028..cf8b8981 100644 --- a/profiles/tdx/test_vars.go +++ b/profiles/tdx/test_vars.go @@ -279,8 +279,8 @@ const ( "value": { "set-operator": "member", "set-digest": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", - "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" + [1, "h0KPxSKAPTEGXnvOPPA_5HUJZjHl4Hu9eg_eYMTPJcc"], + [8, "oxT8LcZjrnpra8Z4dZQFc5bms_VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag"] ] } }, @@ -385,7 +385,7 @@ const ( "value": { "set-operator": "member", "set-digest": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=" + [1, "h0KPxSKAPTEGXnvOPPA_5HUJZjHl4Hu9eg_eYMTPJcc"] ] } }, @@ -394,8 +394,8 @@ const ( "value": { "set-operator": "member", "set-digest": [ - "sha-256:h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc=", - "sha-512:oxT8LcZjrnpra8Z4dZQFc5bms/VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag==" + [1, "h0KPxSKAPTEGXnvOPPA_5HUJZjHl4Hu9eg_eYMTPJcc"], + [8, "oxT8LcZjrnpra8Z4dZQFc5bms_VpzVD9XdtNG7r9K2qjFPwtxmOuemtrxnh1lAVzluaz9WnNUP1d200buv0rag"] ] } } From a5c004d49937dbfc2e6e457e04ba386492d57d01 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Mon, 18 May 2026 14:58:05 +0100 Subject: [PATCH 18/18] test: unmarshal all diag examples from the spec Add tests to unmarshal compiled diag examples from the spec GitHub repository[1]. Specifically, all corim-*.diag and comid-*.diag are added as test case sources in the corim and comid sub-packages respectively, and a test added to each that runs through all compiled examples and ensures that they unmarshal without error. [1]: https://github.com/ietf-rats-wg/draft-ietf-rats-corim/tree/draft-ietf-rats-corim-10/cddl/examples Signed-off-by: Sergei Trofimov --- comid/comid_test.go | 21 +- comid/testcases/comid-1a.cbor | Bin 0 -> 229 bytes comid/testcases/comid-2.cbor | Bin 454 -> 140 bytes comid/testcases/comid-2b.cbor | Bin 0 -> 454 bytes comid/testcases/comid-3.cbor | Bin 152 -> 240 bytes comid/testcases/comid-4.cbor | Bin 229 -> 162 bytes comid/testcases/comid-5.cbor | Bin 233 -> 1100 bytes comid/testcases/comid-6.cbor | Bin 0 -> 140 bytes comid/testcases/comid-7.cbor | Bin 0 -> 108 bytes comid/testcases/comid-cend.cbor | Bin 0 -> 359 bytes comid/testcases/comid-domain-dep.cbor | Bin 0 -> 477 bytes comid/testcases/comid-domain-mem.cbor | Bin 0 -> 326 bytes comid/testcases/comid-flags.cbor | Bin 0 -> 173 bytes .../testcases/comid-integrity-registers.cbor | Bin 0 -> 239 bytes comid/testcases/comid-opaque-instance-id.cbor | Bin 0 -> 155 bytes comid/testcases/comid-raw-value.cbor | Bin 0 -> 279 bytes comid/testcases/comid-series.cbor | Bin 0 -> 468 bytes comid/testcases/src/comid-1.diag | 2 +- comid/testcases/src/comid-1a.diag | 51 ++++ comid/testcases/src/comid-2.diag | 86 +------ comid/testcases/src/comid-2b.diag | 105 +++++++++ comid/testcases/src/comid-3.diag | 34 ++- comid/testcases/src/comid-4.diag | 72 +++--- comid/testcases/src/comid-5.diag | 220 ++++++++++++++---- comid/testcases/src/comid-6.diag | 31 +++ comid/testcases/src/comid-7.diag | 30 +++ comid/testcases/src/comid-cend.diag | 94 ++++++++ comid/testcases/src/comid-design-cd.diag | 181 +++++++------- comid/testcases/src/comid-domain-dep.diag | 133 +++++++++++ comid/testcases/src/comid-domain-mem.diag | 88 +++++++ comid/testcases/src/comid-firmware-cd.diag | 151 ++++++------ comid/testcases/src/comid-flags.diag | 52 +++++ .../src/comid-integrity-registers.diag | 53 +++++ .../src/comid-opaque-instance-id.diag | 28 +++ comid/testcases/src/comid-raw-value.diag | 78 +++++++ comid/testcases/src/comid-series.diag | 172 ++++++++++++++ corim/testcases/corim-1.cbor | Bin 0 -> 204 bytes corim/testcases/corim-2.cbor | Bin 0 -> 496 bytes corim/testcases/corim-design-cd.cbor | Bin 0 -> 726 bytes corim/testcases/corim-firmware-cd.cbor | Bin 0 -> 386 bytes corim/testcases/corim-roles.cbor | Bin 0 -> 133 bytes corim/testcases/regen-from-src.sh | 17 +- .../signed-corim-with-extensions.cbor | Bin 592 -> 592 bytes corim/testcases/signed-example-corim.cbor | Bin 595 -> 595 bytes corim/testcases/signed-good-corim.cbor | Bin 516 -> 516 bytes corim/testcases/src/corim-1.diag | 47 ++++ corim/testcases/src/corim-2.diag | 114 +++++++++ corim/testcases/src/corim-design-cd.diag | 140 +++++++++++ corim/testcases/src/corim-firmware-cd.diag | 92 ++++++++ corim/testcases/src/corim-roles.diag | 42 ++++ .../unsigned-corim-with-extensions.cbor | Bin 422 -> 422 bytes corim/testcases/unsigned-example-corim.cbor | Bin 425 -> 425 bytes corim/testcases/unsigned-good-corim.cbor | Bin 346 -> 346 bytes corim/unsignedcorim_test.go | 17 ++ 54 files changed, 1812 insertions(+), 339 deletions(-) create mode 100644 comid/testcases/comid-1a.cbor create mode 100644 comid/testcases/comid-2b.cbor create mode 100644 comid/testcases/comid-6.cbor create mode 100644 comid/testcases/comid-7.cbor create mode 100644 comid/testcases/comid-cend.cbor create mode 100644 comid/testcases/comid-domain-dep.cbor create mode 100644 comid/testcases/comid-domain-mem.cbor create mode 100644 comid/testcases/comid-flags.cbor create mode 100644 comid/testcases/comid-integrity-registers.cbor create mode 100644 comid/testcases/comid-opaque-instance-id.cbor create mode 100644 comid/testcases/comid-raw-value.cbor create mode 100644 comid/testcases/comid-series.cbor create mode 100644 comid/testcases/src/comid-1a.diag create mode 100644 comid/testcases/src/comid-2b.diag create mode 100644 comid/testcases/src/comid-6.diag create mode 100644 comid/testcases/src/comid-7.diag create mode 100644 comid/testcases/src/comid-cend.diag create mode 100644 comid/testcases/src/comid-domain-dep.diag create mode 100644 comid/testcases/src/comid-domain-mem.diag create mode 100644 comid/testcases/src/comid-flags.diag create mode 100644 comid/testcases/src/comid-integrity-registers.diag create mode 100644 comid/testcases/src/comid-opaque-instance-id.diag create mode 100644 comid/testcases/src/comid-raw-value.diag create mode 100644 comid/testcases/src/comid-series.diag create mode 100644 corim/testcases/corim-1.cbor create mode 100644 corim/testcases/corim-2.cbor create mode 100644 corim/testcases/corim-design-cd.cbor create mode 100644 corim/testcases/corim-firmware-cd.cbor create mode 100644 corim/testcases/corim-roles.cbor create mode 100644 corim/testcases/src/corim-1.diag create mode 100644 corim/testcases/src/corim-2.diag create mode 100644 corim/testcases/src/corim-design-cd.diag create mode 100644 corim/testcases/src/corim-firmware-cd.diag create mode 100644 corim/testcases/src/corim-roles.diag diff --git a/comid/comid_test.go b/comid/comid_test.go index ff68aa77..d998bbd9 100644 --- a/comid/comid_test.go +++ b/comid/comid_test.go @@ -1,8 +1,10 @@ -// Copyright 2024 Contributors to the Veraison project. +// Copyright 2024-2026 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package comid import ( + "os" + "path/filepath" "slices" "testing" @@ -104,3 +106,20 @@ func TestComid_iterators(t *testing.T) { assert.Equal(t, *slices.Collect(c.IterRefVals())[0], c.Triples.ReferenceValues.Values[0]) assert.Equal(t, *slices.Collect(c.IterEndVals())[0], c.Triples.EndorsedValues.Values[0]) } + +func TestComid_unmarshal_RFC_examples(t *testing.T) { + files, err := filepath.Glob("testcases/comid-*.cbor") + require.NoError(t, err) + + for _, path := range files { + t.Run(path, func(t *testing.T) { + data, err := os.ReadFile(path) // nolint:gosec + require.NoError(t, err) + + var cd Comid + + err = cd.FromCBOR(data) + assert.NoError(t, err) + }) + } +} diff --git a/comid/testcases/comid-1a.cbor b/comid/testcases/comid-1a.cbor new file mode 100644 index 0000000000000000000000000000000000000000..8749e6b02fcf9b25973247b706368d9b2c726ce0 GIT binary patch literal 229 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@HWEGA?3R#E@#JXP{@mDCxk!)Y!xrq2RL0IP1%4krkEBdck*73y=Sr ceP;dkc#oz?HQl_$OKwU-bs3@R`cFU?0M6@F5&!@I literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-2.cbor b/comid/testcases/comid-2.cbor index 4c8e045ce4036cc14c3a5b77c04a1fff95553855..45dd4bd628d97a39977d3af56f655a3df87287ce 100644 GIT binary patch delta 30 hcmX@c+`~A*o^j#iCPr()riF|P8E-Oa07)>-1OS_A2|WM+ literal 454 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@t_Kyoh5R�u2HVZ6zt!3Y4K7o=4H diff --git a/comid/testcases/comid-2b.cbor b/comid/testcases/comid-2b.cbor new file mode 100644 index 0000000000000000000000000000000000000000..4c8e045ce4036cc14c3a5b77c04a1fff95553855 GIT binary patch literal 454 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@t_Kyoh5R�u2HVZ6zt!3Y4K7o=4H literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-3.cbor b/comid/testcases/comid-3.cbor index 4277e67af9c91f6d6145363a4c2f890c3356e6d9..494cb17c384defa802cacc12b7b0ba9ecf7633f7 100644 GIT binary patch delta 112 zcmbQi_3>o7h}dpMB4;h#@PtGCnmYH8(Y{1S-z(@6N783^(#! mLYWRQqKc^oq;KlZF*)O~aH9C?Xx52x7D)07p=?1VV;2CshASoj delta 24 gcmeysID>IQyI|uY21%wpj0>3>o7h}dpMB2&0Bx!X1poj5 diff --git a/comid/testcases/comid-4.cbor b/comid/testcases/comid-4.cbor index 8749e6b02fcf9b25973247b706368d9b2c726ce0..27c37b72a8f0c5c0d10a75108bed26a5ba32861c 100644 GIT binary patch delta 96 zcmaFLxQLN)(L_d(iFQ`OjSCqU@;2XO(kf0$EKW5uiO){0jCXYQb&dCRjJV09T>=$O VPAw`y64$AaM3yK>EXjb&0su?9As7Gv delta 164 zcmZ3)_>_@x@kB-uO{T`h44ID3zOD+MdC7W=Hxx=TN=gcft@QO1lXFw`QY#X33vyDK z8W|>vTIDt^WL(6sh#}Qb&p^+BQPP2dsj-PMLcwK~an_g9A}cDL^@8uF79RgK`^@_7 Y@g7Z)YPxxgm)w+w>M}yr^`C$)01{I@Bme*a diff --git a/comid/testcases/comid-5.cbor b/comid/testcases/comid-5.cbor index f4f514c9e494ea35606852b35f90257fec08d809..c07f413c67d1cceedc87c90e3c79b0e460e2363a 100644 GIT binary patch literal 1100 zcmZ3)xR4>ho^5^dN*lo^)2A~8giR>7XIae9*tC#gA;S&Tfb>n>IVNWu7ETmj9nCsX z&a!b4LwZR@W?s625#vJMrkhMUMM;Upsb(he$*Dyp@db$`8SxQVMI)J7fQBx?VyHP| zrlYg3tAb}HCxm=aW)mzP?^%-DXDNh=3xNp@-_$aw8sWJ@El+81?`NwDAZ2L;lr>`yLJN`tZ6PeU4RIJ}Ns@6$v4>rsBi68V!eO}sc0+I^fQ5|MmE*9J jnauRkOlEq);YS1Pj>hFjLh)rtl5tdyFC&sHCo#SNA;F}* delta 113 zcmX@Z@sg2o@kB-uU8csx44ID3zOD+MdC7W=Hxx=TN=gcft@QO1lXFw`QY#X33vyDK z8W~s?GBi%iGZSrE#K6e7h+z>!s-d2No&lqz0|Qgz#QQ3eix`+7;zkJZCdLSb|C4PQ FhLj8fY>Rnf#%~|PlCRu%zXKGx`km=~`>#E?Hm#oKlL!l(2q@n>IVNWu7ETmj9nCsX&K%i-d^Gc!85E@zj?ckXYm@?v6dc>R)*fn^~} zYtur8#SAy{J=oX-s~P!|7#Xu8BBS(DD-v@Ha#EQJft>iD{QMH#{50K?qSE4$MpX4o zgw&&%Pf$IY`2^jAYCZ=eW4=#*Vv1*8GRzOmj9?>gG8s4>xW||s5EulLVJZZ2P<@AH z8WS$l-~oo!LR3>YnX#L)gyDv2K>DWc9FsE+3nz-Nj%J-GXU>@E=_{-Cu0 literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-domain-mem.cbor b/comid/testcases/comid-domain-mem.cbor new file mode 100644 index 0000000000000000000000000000000000000000..29fe0ba343ff0d196d414266810ca4e29a45f86a GIT binary patch literal 326 zcmZ3?xR4=0Zq3zcUl#jso4J48);=yjcjm@L3<1->E@zj?ckXYm@?v6dc>R)*fn_0U zbJIeG#SAy{J=oX-s~P!|m>9DoBBS(DD-v@Ha#EQJft>iD{QMH#{50K?qSE4$#)S-v zkkm7xs0SN%lgYs8z&*z7fWRPxIY17oIjHt>Ff!)*EGmG8 zEMd5z8j!xJJICaV!@`N;tD{*b%9%4}Iy(EhDu7I5$_F!pfQAQ^=H;apF*5?afMhTy HGZupZKtOJa literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-flags.cbor b/comid/testcases/comid-flags.cbor new file mode 100644 index 0000000000000000000000000000000000000000..25d6d81c11dd70f957c6b7e625c3a88e61b4bc15 GIT binary patch literal 173 zcmZ3&xR4=0Zq3zcUl#jso4J48);=yjPo~Dj45|LEzPgT#Hxx=UN=gcft@QQtQ*(6_ z^-?Pma|?1(nHm|Gn-*bcb!TJ%3a*#AF?C;s*X`{u*Iw~TUHzGnfn_0MW79&Q+>Lxc zHlBnw55~41zKr}V$&8EzY2~?T>6v-SFryn6GA?9Z#qgE!E7MozFDzeKzp{O0|H|=| G^D6)hNk;|% literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-integrity-registers.cbor b/comid/testcases/comid-integrity-registers.cbor new file mode 100644 index 0000000000000000000000000000000000000000..cd58c7a85d2adc012207360466341df04569a34f GIT binary patch literal 239 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@C-vGA`s>#L(2l7@^>@$~f!GX^|C`&U(RjQwxv(ntf*d_IQt`NHyKO z#Y=8VH)ZBl>L%u->t?37+*`ZveJYTfSp?P^u*yX0=V^r%l`eb{cT*b=`Y-fG(TlA0 I-@jkK06KG*PYY94}bC{gU&M}eUMp#Y))Wic)= JE-(QZKmhs=JhK1* literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-raw-value.cbor b/comid/testcases/comid-raw-value.cbor new file mode 100644 index 0000000000000000000000000000000000000000..e7080df629685fccbb3deab81da71f1859bef9f7 GIT binary patch literal 279 zcmZ3?xR4>ho^5^dN*lo^)2A~8giR>7XKGx`km=~`>#E?Hm#oKlL!l(2q@C-vGA?Af$zk%OU?AI|MNqdeFtCCR0sscG BT?haG literal 0 HcmV?d00001 diff --git a/comid/testcases/comid-series.cbor b/comid/testcases/comid-series.cbor new file mode 100644 index 0000000000000000000000000000000000000000..ca43956998dd58178d7c04d8e5f69b783303a794 GIT binary patch literal 468 zcmZ3?xR9YjA-7UDuh=RvIX6|eC_gc!s5CDxwMe(Pw4fj-H8(Y{gsE{cL#Cs%ud9M* zUa}tJ4TX}7l9GaAD}8;C9=+6x#9W|Cre;P4CYFU9O-;=U85T3#$ae{4I>5k)(8yFF z0X8HEXh#s(4h6T&qTKStqSVHPj0>3;GJI{k$)r`5lvtc PQC_RoT can be inferred / + +/ Use case (3) - module Extension or call graph trust / +/ ┌──────────────────────┐ has trustee ┌───────────────┐ / +/ │ RoadRunner Extension │ ──────────────────────▶ │ RoadRunner │ / +/ │ (LoadInc.example) │ │ (ACME Inc.) │ / +/ └──────────────────────┘ └───────────────┘ / + +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E47' + }, + / comid.linked-tags / 3 : [ { + / comid.linked-tag-id / 0 : h'97F5A7071C6F438F877A4A020780EBE9', + / comid.tag-rel / 1 : / comid.supplements / 0 + } ], + / triples / 4 : { + / dependency-triples / 4 : [ + [ / ** XYZ Root of Trust fragment A ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F6201'), / 2.1.123.1.15.98.1 / + / comid.vendor / 1 : "XYZ.example", + / comid.model / 2 : "XYZ_Root-of-trust" + } + }, + [ / ** trustees ** / + / ** XYZ Root of Trust fragment B ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F6202'), / 2.1.123.1.15.98.2 / + / comid.vendor / 1 : "XYZ.example", + / comid.model / 2 : "XYZ_Root-of-trust" + } + } + ] + ], + [ / ** XYZ Root of Trust fragment B ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F6202'), / 2.1.123.1.15.98.2 / + / comid.vendor / 1 : "XYZ.example", + / comid.model / 2 : "XYZ_Root-of-trust" + } + }, + [ / ** trustees ** / + / ** XYZ Root of Trust fragment A ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F6201'), / 2.1.123.1.15.98.1 / + / comid.vendor / 1 : "XYZ.example", + / comid.model / 2 : "XYZ_Root-of-trust" + } + } + ] + ], + [ / ** PQR Layer 1 loader module 1 ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F0801'), / 2.1.123.1.15.8.1 / + / comid.vendor / 1 : "LoadInc.example", + / comid.layer / 3 : 1 + } + }, + [ / ** trustee modules ** / + / ** PQR Root of Trust module ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-bytes / 560(h'c0de'), + / comid.vendor / 1 : "PQR.example", + / comid.model / 2 : "PQR_Root-of-trust" + } + } + ] + ], + [ / ** PQR Layer 1 loader module 2 ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F0802'), / 2.1.123.1.15.8.2 / + / comid.vendor / 1 : "LoadInc.example", + / comid.layer / 3 : 1 + } + }, + [ / ** trustee modules ** / + / ** PQR Layer 1 loader module 1 ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F0801'), / 2.1.123.1.15.8.1 / + / comid.vendor / 1 : "LoadInc.example", + / comid.layer / 3 : 1 + } + } + ] + ], + [ / ** L1 RoadRunner Extension module 1 ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-oid-type / 111(h'0607517B010F0903'), / 2.1.123.1.15.9.3 / + / comid.vendor / 1 : "LoadInc.example", + / comid.layer / 3 : 1 + } + }, + [ / ** trustee modules ** / + / ** Layer 1 RoadRunner module ** / + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : / tagged-uuid-type / 37(h'67b28b6c34cc40a19117ab5b05911e37'), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + } + ] + ] + ] + } +} \ No newline at end of file diff --git a/comid/testcases/src/comid-domain-mem.diag b/comid/testcases/src/comid-domain-mem.diag new file mode 100644 index 00000000..5b0b778f --- /dev/null +++ b/comid/testcases/src/comid-domain-mem.diag @@ -0,0 +1,88 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E47' + }, + / comid.linked-tags / 3 : [ { + / comid.linked-tag-id / 0 : h'97F5A7071C6F438F877A4A020780EBE9', + / comid.tag-rel / 1 : / comid.supplements / 0 + } + ], + / triples / 4 : { + / membership-triples / 5 : [ + [ + / environment-map / { + / ** A Root of Trust module ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'0607517B010F6202'), / 2.1.123.1.15.98.2 / + / comid.vendor / 1 : "XYZ.example", + / comid.model / 2 : "XYZ_Root-of-trust" + } + }, + [ + / environment-map / { + / ** A Root of Trust module ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'0607517B010F6201'), / 2.1.123.1.15.98.1 / + / comid.vendor / 1 : "XYZ.example" + } + } + ] + ], + [ / environment-map / { + / ** A Root of Trust module ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-bytes / 560(h'c0de'), + / comid.vendor / 1 : "PQR.example", + / comid.model / 2 : "PQR_Root-of-trust" + } + }, + [ + / environment-map / { + / ** Layer 1 loader module 1 ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'0607517B010F0801'), / 2.1.123.1.15.8.1 / + / comid.vendor / 1 : "LoadInc.example", + / comid.layer / 3 : 1 + } + }, + / environment-map / { + / ** Layer 1 loader module 2 ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'0607517B010F0802'), / 2.1.123.1.15.8.2 / + / comid.vendor / 1 : "LoadInc.example", + / comid.layer / 3 : 1 + } + } + ] + ], + [ / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / environment-map / { + / ** L1 Extension module 1 ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'0607517B010F0903'), / 2.1.123.1.15.9.3 / + / comid.vendor / 1 : "LoadInc.example", + / comid.layer / 3 : 1 + } + } + ] + ] + ] + } +} \ No newline at end of file diff --git a/comid/testcases/src/comid-firmware-cd.diag b/comid/testcases/src/comid-firmware-cd.diag index d61ab7db..a18c3168 100644 --- a/comid/testcases/src/comid-firmware-cd.diag +++ b/comid/testcases/src/comid-firmware-cd.diag @@ -1,86 +1,83 @@ - - / concise-mid-tag / { - / comid.tag-identity / 1 : { - / comid.tag-id / 0 : h'AF1CD895BE784ADBB7E9ADD44A65ABF3' - }, - / comid.entity / 2 : [ { - / comid.entity-name / 0 : "Firmware MFG Inc.", - / comid.reg-id / 1 : 32("https://fwmfginc.example"), - / comid.role / 2 : [ 0 ] / tag-creator / - } ], - / comid.triples / 4 : { - / comid.reference-triples / 0 : [ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'AF1CD895BE784ADBB7E9ADD44A65ABF3' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "Firmware MFG Inc.", + / comid.reg-id / 1 : 32("https://fwmfginc.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / ** Hash of layer 0 firmware ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwY_n5x", + / comid.layer / 3 : 0, + / comid.index / 4 : 0 + } + }, [ - / environment-map / { - / ** Hash of layer 0 firmware ** / - / comid.class / 0 : { - / comid.vendor / 1 : "fwmfginc.example", - / comid.model / 2 : "fwY_n5x", - / comid.layer / 3 : 0, - / comid.index / 4 : 0 + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'15E77D6F133252F1DB7044901313884F2977D2109B33C79F33E079BFC78865255C0FB733C240FDDA544B8215D7B8F815' + ] + ] } - }, - [ - / measurement-map / - { - / comid.mval / 1 : { - / comid.svn / 1 : 552(1), - / comid.digests / 2 : [ - [ - / hash-alg-id / 7, / SHA384 / - / hash-value / h'15E77D6F133252F1DB7044901313884F2977D2109B33C79F33E079BFC78865255C0FB733C240FDDA544B8215D7B8F815' - ] - ] - } - } - ] - ], - [ - / environment-map / { - / ** Hash of layer 1 firmware ** / - / comid.class / 0 : { - / comid.vendor / 1 : "fwmfginc.example", - / comid.model / 2 : "fwX_n5x", - / comid.layer / 3 : 1, - / comid.index / 4 : 0 - } - }, - [ - / measurement-map / - { - / comid.mval / 1 : { - / comid.svn / 1 : 552(1), - / comid.digests / 2 : [ - [ - / hash-alg-id / 7, / SHA384 / - / hash-value / h'3D90B6BF003DA2D94EA5463F97FB3C53DDC51CFBA1E3E38EEF7AF071A67986595D22729131DF9FE80F5451EEF154F85E' - ] - ] - } - } - ] + } ] ], - / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / ** Hash of layer 1 firmware ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwX_n5x", + / comid.layer / 3 : 1, + / comid.index / 4 : 0 + } + }, [ - / environment-map / { - / comid.class / 0 : { - / ** Firmware is valid (example) ** / - / comid.class-id / 0 : - / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / - / comid.vendor / 1 : "fwmfginc.example" - } - }, - [ - / measurement-map / { - / comid.mval / 1 : { - / raw-value-group / - / comid.raw-value / 4 : 560(h'0000000000000000'), - / comid.raw-value-mask / 5 : h'FFFFFFFF00000000' - } + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'3D90B6BF003DA2D94EA5463F97FB3C53DDC51CFBA1E3E38EEF7AF071A67986595D22729131DF9FE80F5451EEF154F85E' + ] + ] } - ] + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / comid.class / 0 : { + / ** Firmware is valid (example) ** / + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fwmfginc.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask-DEPRECATED / 5 : h'FFFFFFFF00000000' + } + } ] ] - } + ] } +} diff --git a/comid/testcases/src/comid-flags.diag b/comid/testcases/src/comid-flags.diag new file mode 100644 index 00000000..97c90efc --- /dev/null +++ b/comid/testcases/src/comid-flags.diag @@ -0,0 +1,52 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E49' + }, + / comid.entities / 2 : [ { + / comid.entity-name / 0 : "OEM-A", + / comid.reg-id / 1 : 32("https://oem-a.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.linked-tags / 3 : [ + / linked-tag-map / { + / comid.linked-tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E47', + / comid.tag-rel / 1 : / comid.supplements / 0 + }, + / linked-tag-map / { + / comid.linked-tag-id / 0 : h'AF1CD895BE784ADBB7E9ADD44A65ABF3', + / comid.tag-rel / 1 : / comid.supplements / 0 + } + ], + / comid.triples / 4 : { + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / comid.class / 0 : { + / ** Firmware is valid (example) ** / + / comid.class-id / 0 : + / tagged-oid-type / 111(h'060C6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fwmfginc.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.flags / 3 : { + / configured / 0 : true, + / secure / 1 : true, + / recovery / 2 : true, + / debug / 3 : false, + / replay-protected / 4 : true, + / integrity-protected / 5 : true, + / runtime-meas / 6 : true, + / immutable / 7 : true, + / tcb / 8 : true, + / confidentiality-protected / 9 : true + } + } + } + ] + ] + ] + } +} \ No newline at end of file diff --git a/comid/testcases/src/comid-integrity-registers.diag b/comid/testcases/src/comid-integrity-registers.diag new file mode 100644 index 00000000..3081deab --- /dev/null +++ b/comid/testcases/src/comid-integrity-registers.diag @@ -0,0 +1,53 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.integrity-registers / 14 : { + 0: [ + [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ], + [ + / hash-alg-id / "my-alg-id", + / hash-value / h'deadbeef' + ] + ], + "my-ir": [ + [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'50aa341af9cb20a879440e58dd6581c14fa14bccafb75f488259262d6ea3a4d9' + ], + [ + / hash-alg-id / "my-alg-id", + / hash-value / h'fefefafa' + ] + ] + } + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-opaque-instance-id.diag b/comid/testcases/src/comid-opaque-instance-id.diag new file mode 100644 index 00000000..bac70e7f --- /dev/null +++ b/comid/testcases/src/comid-opaque-instance-id.diag @@ -0,0 +1,28 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.instance / 1 : / e.g., SEV-SNP CHIP_ID / 560( + h'9f71ec4d223f4f899d532ed6ff6ecbbb4a62cb386ba24c204c9371ce5e3b9291713fe96b9b413d8842968ebb1fa4cf1920d0c5e9f872776a1e826f2851ecdb47') + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + } + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-raw-value.diag b/comid/testcases/src/comid-raw-value.diag new file mode 100644 index 00000000..6aecf169 --- /dev/null +++ b/comid/testcases/src/comid-raw-value.diag @@ -0,0 +1,78 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + + / The reference triples below all match against an ACS entry containing a raw value. / + / The first matches against the complete raw value, the next two match against part of the value / + + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / This version compares the whole 32 bits / + / comid.mval / 1 : { + / comid.raw-value / 4 : 560(h'12345678') + } + } + ] + ], [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / This version compares the first 16 bits using preferred syntax / + / comid.mval / 1 : { + / comid.raw-value / 4 : 563( [ / value / h'12340000', / mask / h'FFFF0000' ] ) + } + } + ] + ], [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / This version compares the first 16 bits using deprecated syntax / + / comid.mval / 1 : { + / comid.raw-value / 4 : 560(h'12340000'), + / comid.raw-value-mask-DEPRECATED / 5 : h'FFFF0000' + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-series.diag b/comid/testcases/src/comid-series.diag new file mode 100644 index 00000000..48d4ed38 --- /dev/null +++ b/comid/testcases/src/comid-series.diag @@ -0,0 +1,172 @@ +/ concise-mid-tag / { + / tag-identity / 1 : { + / tag-id / 0 : "my-ns:acme-roadrunner-supplement" + }, + / entity / 2 : [ { + / entity-name / 0 : "ACME Inc.", + / reg-id / 1 : 32("https://acme.example"), + / role / 2 : [ 1,0,2 ] / creator, tag-creator, maintainer / + } ], + / triples / 4 : { + / conditional-endorsement-series-triples / 8 : [ + [ / *** first triple *** / + [ / *** Condition *** / + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-oid-type / 111(h'5502C000'), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner Firmware" + } + }, + / claims-list / [ + { / *** measurement-map *** / + / mval / 1 : / measurement-values-map / { + / comid.flags / 3 : { + / configured / 0 : true + } + } + } + ], + / authorized-by / [ + / tagged-pkix-base64-key-type / 554("base64_key_ACME_signer") + ] + ], /*** end Condition ***/ + [ / *** Series *** / + [ / conditional-series-record #1 / + [ / *** Selection *** / + { / *** ref-val measurement-map *** / + / mval / 1 : / measurement-values-map / { + / ver / 0 : { + / version / 0 : "2.0.0" + }, + / comid.svn / 1 : 552(3) + } + } + ], + [ / *** Addition *** / + { / *** endv-measurement-map *** / + / mval / 1 : / measurement-values-map / { + / name / 11: "-NO_CVE-" + } + } + ] + ], + [ / conditional-series-record #2 / + [ / *** Selection *** / + { / *** ref-val measurement-map *** / + / mval / 1 : / measurement-values-map / { + / ver / 0 : { + / version / 0 : "1.0.0" + }, + / comid.svn / 1 : 552(2) + } + } + ], + [ / *** Addition *** / + { / *** endv-measurement-map *** / + / mval / 1 : / measurement-values-map / { + / name / 11: "CVE_WARNING" + } + } + ] + ], + [ / conditional-series-record #3 / + [ / *** Selection *** / + { / *** ref-val measurement-map *** / + / mval / 1 : / measurement-values-map / { + / ver / 0 : { + / version / 0 : "1.0.0" + }, + / comid.svn / 1 : 552(1) + } + } + ], + [ / *** Addition *** / + { / *** endv-measurement-map *** / + / mval / 1 : / measurement-values-map / { + / name / 11: "CVE_VULNERABLE" + } + } + ] + ] + ] / *** end series *** / + ], + [ / *** second triple *** / + [ / *** Condition *** / + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-oid-type / 111(h'5502C000'), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner Firmware" + } + }, + / claims-list / [], + / authorized-by / [ + / tagged-pkix-base64-key-type / 554("base64_key_ACME_signer") + ] + ], /*** end Condition ***/ + [ / *** Series *** / + [ / conditional-series-record #1 / + [ / *** Selection *** / + { / *** ref-val measurement-map *** / + / mval / 1 : / measurement-values-map / { + / ver / 0 : { + / version / 0 : "2.0.0" + }, + / comid.svn / 1 : 552(3) + } + } + ], + [ / *** Addition *** / + { / *** endv-measurement-map *** / + / mval / 1 : / measurement-values-map / { + / name / 11: "-NO_CVE-" + } + } + ] + ], + [ / conditional-series-record #2 / + [ / *** Selection *** / + { / *** ref-val measurement-map *** / + / mval / 1 : / measurement-values-map / { + / ver / 0 : { + / version / 0 : "1.0.0" + }, + / comid.svn / 1 : 552(2) + } + } + ], + [ / *** Addition *** / + { / *** endv-measurement-map *** / + / mval / 1 : / measurement-values-map / { + / name / 11: "CVE_WARNING" + } + } + ] + ], + [ / conditional-series-record #3 / + [ / *** Selection *** / + { / *** ref-val measurement-map *** / + / mval / 1 : / measurement-values-map / { + / ver / 0 : { + / version / 0 : "1.0.0" + }, + / comid.svn / 1 : 552(1) + } + } + ], + [ / *** Addition *** / + { / *** endv-measurement-map *** / + / mval / 1 : / measurement-values-map / { + / name / 11: "CVE_VULNERABLE" + } + } + ] + ] + ] / *** end series *** / + ] + ] + } +} diff --git a/corim/testcases/corim-1.cbor b/corim/testcases/corim-1.cbor new file mode 100644 index 0000000000000000000000000000000000000000..0cd6ca368e86636264b0ca30b48477ca32472721 GIT binary patch literal 204 zcmcb~_;nFOfQDa=UF>}S>{j_GC-+az%NZMQGX9EKznF0$Lx4To`s9^1f={MTX9x(J zP;SrExR@c+(b?Bk!80#ekMV{=Nk&OYL9vy-eqwTNs$ObEVs1fBDpMl^%R+|6riBbk z7;dNrq;KlZF*)O~aH9C?Xx52x=8OmvnDW7f1?4BE1eNCHr4}(WHZEjb#IT4V)lkns y&wx?Vfq|*9i7`UKWtDN(m(wCEDxLL$@1_}S>{j_GC-+az%NZMQGX9EWytJ5cAwz&Y+xp~{HiA#4PiF`S zn^11g)VP=-)6v=2RlzeaS CLPS)%9a^{Q(6PPL_z@`P|C#D3I=H;apDY#`8<(4NFr7|-% zE@WKD)Y!xrq2RL0IP1%4krkEBdck*73y=SreP;dkc#oz?HQl_$OKwU7ZCVPlX}PqW zMX#$DYvbpjr#;O&F^pN^kv^Uf$1;^e*v|Qt`6a0eAw{LdC8;S2{=v*lEDQ*%b{9(a zUE`X1$0l)JXp9bbZ)wWn2Ez>|3#WFfPCOQzx`9%wkbFhRD#k{lLQobx1cUPPOBC|c PAYNn!`;GA?lLjLI@aeS_ literal 0 HcmV?d00001 diff --git a/corim/testcases/corim-design-cd.cbor b/corim/testcases/corim-design-cd.cbor new file mode 100644 index 0000000000000000000000000000000000000000..ba98869765aef07e3339fb6279224f827f2cc958 GIT binary patch literal 726 zcmcb~_;m?G0GICEp0Mu@gC=O z_Hp^SGc_(|D0B;OcT{jmEzV5OE7lFt4J~H8p->^0QBqP+Y^ATCR*;^U0+B2#E!In| zNX#wBNo8teU~XK*5HS7ga(0<~=l=F8FDCYe*Do0vSQas~G%aLU%y1*$JE6^kvF(R1 zBR>lxV;OdXn3)~u8k^W74D28FopK0npT}7D z^>vk9V0h}B_}LqyYdNRR?rw8$((*YZW3{YE=6(Cu{Yy=z-sUjIw1$}&YZU%xNO3eh zn(ZMO+|g;KFlVdn&!el}BxLEREch+lI#tNK&t7Grdf^_6kTV%um=o6mtyu&N6(2;X zB;yPgSfE50z(R#snl-|}ElI`1Hba8xU_kc1>(@VUPn*5=ZnE2xPtVVZq)nQcue2mW zV@b^}Tca!M#9X~{I+YkY^ff2(7 z+X>tDGuSS=>9^F)e)?~l;JZgp#Xt`p#Y%( n*d /dev/null && pwd ) + GEN_TESTCASE=$(go env GOPATH)/bin/gen-testcase +if [[ "$(type -p diag2cbor.rb)" == "" ]]; then + echo "ERROR: please install ruby-cbor-diag package" + exit 1 +fi + if [[ ! -f ${GEN_TESTCASE} ]]; then echo "installing gen-testcase" go install github.com/veraison/gen-testcase@v0.0.3 @@ -27,4 +34,12 @@ for case in "${testcases[@]}"; do -o "signed-${case}.cbor" done +for case in "$THIS_DIR"/src/*.diag; do + outfile=$(basename "${case%%.diag}").cbor + + echo "generating $outfile" + + diag2cbor.rb "$case" > "$THIS_DIR/$outfile" +done + echo "done." diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor index 8b07a037c89758650e9386b5ba02497d2fce4f7b..7afa5c5d2546c7a0cf56d7204d0421ddfc55643b 100644 GIT binary patch delta 119 zcmV--0Eqw41kePKCJq3>8ez^z00F@oX;f!`u{06^lgt4elTHC9lgj}elcfPY1|S+{ zkqvH>;sGW?SU@RYF8{+iTKlNhWcoZdo2Ga^bz|MpNwA|rfDFLif3Hw%qG}T!K!Yvd ZX!k2l@NZv$ubv~yilK1VkkzGx9~YavGA#fA delta 135 zcmV;20C@k<1kePKCJq6?8fjE#fB?Z7Va`YZu{06^lUe~T6ad&IP%-;jzXCo-`jdzOT9cmv29xdqAV?q@W|0kUSU~nD=p%mPkpnXhR-433P16F@k`DT|GnOBB p10)q5&op0N5o^G5cD- z0zOHSs!o;p!B9K;lgA&p#GxZBXpseQC$1Gv#{d8T delta 208 zcmV;>05AX31k(hNHjzvi9sqPfLrp~>Omt)*b7^O8X>MmAYh`($Splip0rjMl+W|HX zAY*24ZvlbX0s2`1Vx*J60UHPcfusV1009D#eKZIJp#Xt`p#YOmt)*b7^O8X>MmAYh`($Spizv0rjGhdnb{n zBN+mN009C7p#Xt`p#Y--YEo}uWKwl*Ze?wLB~UT@TE7B5Ns_8g zmHEL?JNlCg0UeWN0bNl5ZFFUGbRc7Ia%pWKX=GSH>xv8(`8>ahGO%^PNu>CyGqxcP v*S4X~`g6Y> ) + ] + } +) diff --git a/corim/testcases/src/corim-2.diag b/corim/testcases/src/corim-2.diag new file mode 100644 index 00000000..af56676b --- /dev/null +++ b/corim/testcases/src/corim-2.diag @@ -0,0 +1,114 @@ +/ tagged-unsigned-corim-map / 501({ + / corim.id / 0 : h'284e6c3e5d9f4f6b851f5a4247f243a7', + / corim.tags / 1 : [ + / concise-mid-tag / 506( << + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "ACME Inc.", + / comid.reg-id / 1 : 32("https://acme.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Firmware", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] + ], + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'a71b3e388d454a0581f352e58c832c5c' + ), + / comid.vendor / 1 : "WYLIE Inc.", + / comid.model / 2 : "WYLIE Coyote Trusted OS", + / comid.layer / 3 : 2, + / comid.index / 4 : 0 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' + ] ] + } + } + ] + ], + [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'a71b3e388d454a0581f352e58c832c5c' + ), + / comid.vendor / 1 : "WYLIE Inc.", + / comid.model / 2 : "WYLIE Coyote Trusted OS", + / comid.layer / 3 : 2, + / comid.index / 4 : 1 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'bb71198ed60a95dc3c619e555c2c0b8d7564a38031b034a195892591c65365b0' + ] ] + } + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / comid.vendor / 1 : "ACME Inc.", + / comid.model / 2 : "ACME RoadRunner Root of Trust", + / comid.layer / 3 : 0 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1) + } + } + ] + ] ] + } + } + >> ) + ] + } +) + diff --git a/corim/testcases/src/corim-design-cd.diag b/corim/testcases/src/corim-design-cd.diag new file mode 100644 index 00000000..aecd69ad --- /dev/null +++ b/corim/testcases/src/corim-design-cd.diag @@ -0,0 +1,140 @@ +/ tagged-unsigned-corim-map / 501({ + / corim.id / 0 : h'0A2D9D8C56F74071B4F38065C37E4ACF', + / corim.tags / 1 : [ + / concise-mid-tag / 506( << + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'1EACD596F4A34FB699BFAEB58E0A4E47' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "FPGA Designs-R-Us", + / comid.reg-id / 1 : 32("https://fpgadesignsrus.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.linked-tags / 3 : [ { + / comid.linked-tag-id / 0 : h'97F5A7071C6F438F877A4A020780EBE9', + / comid.tag-rel / 1 : / comid.supplements / 0 + } + ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / ** Layer 3 device state ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0401'), / 2.16.840.1.113741.1.15.4.1 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask-DEPRECATED / 5 : h'FFFFFFFF00000000' + } + } + ] + ], + [ + / environment-map / { + / ** Layer 2 design (IO descriptor) hash ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0402'), / 2.16.840.1.113741.1.15.4.2 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'3FE18ECA4053879E017EF5EB7A3E5157659C5F9BB15B7D09959B8B8647822A4CC21C3AA6721CEF87F5BFA53495DB0833' + ] + ] + } + } + ] + ], + [ + / environment-map / { + / ** Layer 2 design (CORE descriptor) hash ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F0403'), / 2.16.840.1.113741.1.15.4.3 / + / comid.vendor / 1 : "fpgadesignsrus.example", + / comid.layer / 3 : 2 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'20FF681A0882E29B481953888936209CB53DF9C5AAEC606A2C24A0FB138595124B8E3F24A12771BC3854CC68B40361AD' + ] + ] + } + } + ] + ], + [ + / environment-map / { + / ** Firmware is valid (example assertion) ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fpgadesignsrus.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), + / comid.raw-value-mask-DEPRECATED / 5 : h'466224343D681802C1506BBED7D7F00B969BADDD6346E4F2E7CE146692996F22A45814DE81D248F583B65F817B5FCEAB' + } + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / ** Design is valid (example assertion) ** / + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046302'), / 2.16.840.1.113741.1.15.4.99.2 / + / comid.vendor / 1 : "fpgadesignsrus.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask-DEPRECATED / 5 : h'FFFFFFFF00000000' + } + } + ] + ] + ] + } + } + >> ) + ], + / corim.dependent-rims / 2 : [ + { + / ** link to firmware rim file ** / + / corim.href / 0 : 32("https://rims.example.com/path/to/file_adkfhaeria-dfka_efkj.rim") + } + ], + / corim.profile / 3 : + 111(h'6086480186F84D010F06') / 2.16.840.1.113741.1.15.6 / +}) diff --git a/corim/testcases/src/corim-firmware-cd.diag b/corim/testcases/src/corim-firmware-cd.diag new file mode 100644 index 00000000..7041e45c --- /dev/null +++ b/corim/testcases/src/corim-firmware-cd.diag @@ -0,0 +1,92 @@ +/ tagged-unsigned-corim-map / 501({ + / corim.id / 0 : h'29B834181A5C4E4EA53E8F8786BC8C5B', + / corim.tags / 1 : [ + / concise-mid-tag / 506( << + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'AF1CD895BE784ADBB7E9ADD44A65ABF3' + }, + / comid.entity / 2 : [ { + / comid.entity-name / 0 : "Firmware MFG Inc.", + / comid.reg-id / 1 : 32("https://fwmfginc.example"), + / comid.role / 2 : [ 0 ] / tag-creator / + } ], + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ + [ + / environment-map / { + / ** Hash of layer 0 firmware Y ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwY_n5x", + / comid.layer / 3 : 0, + / comid.index / 4 : 0 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'15E77D6F133252F1DB7044901313884F2977D2109B33C79F33E079BFC78865255C0FB733C240FDDA544B8215D7B8F815' + ] + ] + } + } + ] + ], + [ + / environment-map / { + / ** Hash of layer 1 firmware X ** / + / comid.class / 0 : { + / comid.vendor / 1 : "fwmfginc.example", + / comid.model / 2 : "fwX_n5x", + / comid.layer / 3 : 1, + / comid.index / 4 : 0 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1), + / comid.digests / 2 : [ + [ + / hash-alg-id / 7, / SHA384 / + / hash-value / h'3D90B6BF003DA2D94EA5463F97FB3C53DDC51CFBA1E3E38EEF7AF071A67986595D22729131DF9FE80F5451EEF154F85E' + ] + ] + } + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ + [ + / environment-map / { + / comid.class / 0 : { + / ** Firmware is valid (example) ** / + / comid.class-id / 0 : + / tagged-oid-type / 111(h'6086480186F84D010F046301'), / 2.16.840.1.113741.1.15.4.99.1 / + / comid.vendor / 1 : "fwmfginc.example" + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'0000000000000000'), + / comid.raw-value-mask-DEPRECATED / 5 : h'FFFFFFFF00000000' + } + } + ] + ] + ] + } + } + >> ) + ], + / corim.profile / 3 : + 111( h'6086480186F84D010F06') / 2.16.840.1.113741.1.15.6 / +}) diff --git a/corim/testcases/src/corim-roles.diag b/corim/testcases/src/corim-roles.diag new file mode 100644 index 00000000..afbb66ba --- /dev/null +++ b/corim/testcases/src/corim-roles.diag @@ -0,0 +1,42 @@ +/ tagged-corim-map / 501({ + / corim.id / 0 : h'284e6c3e5d9f4f6b851f5a4247f243a7', + / corim.entities / 5 : [ + { + / corim.entity-name / 0 : "OEM-A", + / corim.reg-id / 1 : 32("https://oem-a.example"), + / corim.role / 2 : [ 2 ] / manifest-signer / + } + ], + / corim.tags / 1 : [ + / concise-mid-tag / 506( << + / concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3f06af63a93c11e4979700505690773f' + }, + / comid.triples / 4 : { + / comid.reference-triples / 0 : [ [ + / environment-map / { + / comid.class / 0 : { + / comid.class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ) + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + } + } + } + ] + ] ] + } + } + >> ) + ] + } +) diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor index a65ecd8106ff937340fb5639dd5ae6f945459785..6d7ef72c84e6e2f1be53f52c6cfe24f83afb3c61 100644 GIT binary patch delta 92 zcmV-i0Hgn=1EvE6*#Y&WlXC$xA|PXCZ*KvC*#Y`l0b-;9p#V@ryW@W@VM{Zv(^Jp! yLW>rW5=0UKWI;nsMFMJ4Z((Fob#88Da*qITVi&qLU2h@YHG>Ecnv9*g$#{N3mFzOrZ_tLx-w-29= diff --git a/corim/testcases/unsigned-good-corim.cbor b/corim/testcases/unsigned-good-corim.cbor index f888b902554811f5eafe71b4d9877cacd774955f..2df724b5c46babc58a94ffbfddffca821657ce20 100644 GIT binary patch delta 94 zcmcb`bc;#&Cgayd47nw##U%>K`9+zz3YjSrS=6~1G98_LT@`#vQuHQj%T5fgpZH8e il;MVIfZ^|`{Y-YAlUDgn{dq9J`q!e#%8Z)ASR??A9wop4 delta 121 zcmcb`bc>1QCgayd6NS|^7;h+)WR#Q?6kF-*Cno2n>ZMjB<`(3nGBq`pRO-!m6VYs0hVE8*~Ka-v3q*Z=Xe;y35{x$KThUg+}5|iZ^HTiN&Qj1FzlJko) Ia}_dE0JixqhyVZp diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 5ea46d08..32710967 100644 --- a/corim/unsignedcorim_test.go +++ b/corim/unsignedcorim_test.go @@ -5,6 +5,8 @@ package corim import ( "fmt" + "os" + "path/filepath" "slices" "testing" "time" @@ -527,3 +529,18 @@ func TestComid_iterators(t *testing.T) { ) assert.NoError(t, errFunc()) } + +func TestCorim_unmarshal_RFC_examples(t *testing.T) { + files, err := filepath.Glob("testcases/corim-*.cbor") + require.NoError(t, err) + + for _, path := range files { + t.Run(path, func(t *testing.T) { + data, err := os.ReadFile(path) // nolint:gosec + require.NoError(t, err) + + _, err = UnmarshalAndValidateUnsignedCorimFromCBOR(data) + assert.NoError(t, err) + }) + } +}