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/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/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..314d7ffd 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() { @@ -104,14 +104,11 @@ 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() { - 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) } @@ -156,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 ( @@ -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..43b48ec4 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() { @@ -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 ( @@ -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) } @@ -157,14 +154,11 @@ 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() { - 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/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/cbor.go b/comid/cbor.go index e8540b5d..31c71e12 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(""), @@ -31,6 +30,8 @@ var ( 559: TaggedCertThumbprint{}, 560: TaggedBytes{}, 561: TaggedCertPathThumbprint{}, + 562: TaggedPKIXAsn1DerCert{}, + 563: TaggedMaskedRawValue{}, 564: TaggedRawIntRange{}, } ) @@ -54,9 +55,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/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/comid/comid_test.go b/comid/comid_test.go index a65f407f..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" @@ -51,7 +53,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")), }, }), }), @@ -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/cond_endorse_series_triple.go b/comid/cond_endorse_series_triple.go index b7a7b2b1..7c3fe283 100644 --- a/comid/cond_endorse_series_triple.go +++ b/comid/cond_endorse_series_triple.go @@ -4,13 +4,84 @@ 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) +} + +// nolint:dupl +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 +131,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 +152,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..83d6f471 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) { @@ -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,14 +132,14 @@ 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")), }, }, ), } series := &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: &Class{ ClassID: MustNewUUIDClassID(TestUUID), @@ -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")), }, }, ), @@ -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), @@ -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")), }, }, ), @@ -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), @@ -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")), }, }, ), @@ -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/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/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/cryptokey.go b/comid/cryptokey.go index 0bcfda55..34e1612c 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 @@ -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 ( @@ -35,6 +34,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. @@ -60,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 { @@ -89,6 +90,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() } @@ -107,7 +111,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 } @@ -133,22 +137,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() } @@ -174,6 +172,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 @@ -181,6 +182,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) @@ -190,7 +196,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 { @@ -214,6 +220,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") @@ -248,6 +258,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) @@ -257,7 +272,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 { @@ -281,6 +296,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 { @@ -330,6 +349,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) @@ -340,7 +364,7 @@ func NewPKIXBase64CertPath(k any) (*CryptoKey, error) { return nil, err } - return &CryptoKey{cert}, nil + return &CryptoKey{&cert}, nil } func MustNewPKIXBase64CertPath(k any) *CryptoKey { @@ -366,6 +390,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 { @@ -428,7 +456,7 @@ type TaggedCOSEKey []byte func NewCOSEKey(k any) (*CryptoKey, error) { if k == nil { - return &CryptoKey{TaggedCOSEKey{}}, nil + return &CryptoKey{&TaggedCOSEKey{}}, nil } var b []byte @@ -452,7 +480,7 @@ func NewCOSEKey(k any) (*CryptoKey, error) { return nil, err } - return &CryptoKey{key}, nil + return &CryptoKey{&key}, nil } func MustNewCOSEKey(k any) *CryptoKey { @@ -490,6 +518,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") @@ -566,46 +598,107 @@ func (o TaggedCOSEKey) coseKeySet() ([]*cose.Key, error) { return keySet, nil } -type digest struct { - swid.HashEntry +// 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 (o digest) String() string { - return o.HashEntry.String() +func MustNewPKIXAsn1DerCert(k any) *CryptoKey { + cert, err := NewPKIXAsn1DerCert(k) + if err != nil { + panic(err) + } + return cert } -func (o digest) Valid() error { - return swid.ValidHashEntry(o.HashAlgID, o.HashValue) +func (o TaggedPKIXAsn1DerCert) String() string { + block := &pem.Block{ + Type: "CERTIFICATE", + Bytes: o, + } + + return string(pem.EncodeToMemory(block)) } -func (o digest) PublicKey() (crypto.PublicKey, error) { - return nil, errors.New("cannot get PublicKey from a digest") +func (o TaggedPKIXAsn1DerCert) Valid() error { + _, err := o.cert() + return err +} + +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 { + 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 } // 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 func NewThumbprint(k any) (*CryptoKey, error) { - var he swid.HashEntry + if k == nil { + return &CryptoKey{&TaggedThumbprint{}}, nil + } + + 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 @@ -631,26 +724,31 @@ 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 func NewCertThumbprint(k any) (*CryptoKey, error) { - var he swid.HashEntry + if k == nil { + return &CryptoKey{&TaggedCertThumbprint{}}, nil + } + + 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 @@ -677,26 +775,31 @@ 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 func NewCertPathThumbprint(k any) (*CryptoKey, error) { - var he swid.HashEntry + if k == nil { + return &CryptoKey{&TaggedCertPathThumbprint{}}, nil + } + + 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 @@ -735,6 +838,7 @@ var cryptoKeyValueRegister = map[string]ICryptoKeyFactory{ PKIXBase64CertType: NewPKIXBase64Cert, PKIXBase64CertPathType: NewPKIXBase64CertPath, COSEKeyType: NewCOSEKey, + PKIXAsn1DerCertType: NewPKIXAsn1DerCert, ThumbprintType: NewThumbprint, CertThumbprintType: NewCertThumbprint, CertPathThumbprintType: NewCertPathThumbprint, @@ -777,3 +881,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 38bd6e25..c7dcde18 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 @@ -7,12 +7,13 @@ import ( "crypto" "encoding/base64" "encoding/json" + "errors" "fmt" + "strings" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - "github.com/veraison/swid" ) func Test_CryptoKey_NewPKIXBase64Key(t *testing.T) { @@ -167,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") } @@ -193,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{}) }) } } @@ -215,64 +210,66 @@ func Test_CryptoKey_JSON_roundtrip(t *testing.T) { { Type: BytesType, In: TestTaggedBytes, - Out: string(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), ), }, } { - 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": %s}`, 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) + }) } } @@ -297,15 +294,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: "cannot unmarshal string into Go value of type []interface {}", }, { Val: `{"type": "random-key", "value":"deadbeef"}`, - ErrMsg: "unexpected ICryptoKeyValue type", + ErrMsg: "unexpected CryptoKey type: random-key", }, } { err := key.UnmarshalJSON([]byte(tv.Val)) @@ -408,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", @@ -434,7 +431,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) { @@ -449,10 +446,35 @@ func (o testCryptoKey) String() string { return "test" } +func (o testCryptoKey) Bytes() []byte { + return []byte("test") +} + 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) @@ -474,7 +496,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 @@ -482,3 +504,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/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/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/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/example_test.go b/comid/example_test.go index 61830b7a..e7d6c2f3 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 @@ -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"). @@ -99,7 +98,7 @@ func Example_encode() { ). AddCondEndorseSeries( &CondEndorseSeriesTriple{ - Condition: ValueTriple{ + Condition: CondEndorseSeriesCondition{ Environment: Environment{ Class: NewClassOID(TestOID). SetVendor("ACME Ltd."). @@ -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":[{"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":[[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/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/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") }) + }) + } +} 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/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/measurement.go b/comid/measurement.go index 1ca5abfa..938f57ea 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()) @@ -618,7 +626,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 } @@ -643,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 d996af2e..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, } @@ -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..0b396fd6 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) } @@ -227,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 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 572a3a1c..4ad5c099 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 @@ -180,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") @@ -213,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")), }, }), }), @@ -223,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/testcases/comid-1a.cbor b/comid/testcases/comid-1a.cbor new file mode 100644 index 00000000..8749e6b0 Binary files /dev/null and b/comid/testcases/comid-1a.cbor differ diff --git a/comid/testcases/comid-2.cbor b/comid/testcases/comid-2.cbor index 4c8e045c..45dd4bd6 100644 Binary files a/comid/testcases/comid-2.cbor and b/comid/testcases/comid-2.cbor differ diff --git a/comid/testcases/comid-2b.cbor b/comid/testcases/comid-2b.cbor new file mode 100644 index 00000000..4c8e045c Binary files /dev/null and b/comid/testcases/comid-2b.cbor differ diff --git a/comid/testcases/comid-3.cbor b/comid/testcases/comid-3.cbor index 4277e67a..494cb17c 100644 Binary files a/comid/testcases/comid-3.cbor and b/comid/testcases/comid-3.cbor differ diff --git a/comid/testcases/comid-4.cbor b/comid/testcases/comid-4.cbor index 8749e6b0..27c37b72 100644 Binary files a/comid/testcases/comid-4.cbor and b/comid/testcases/comid-4.cbor differ diff --git a/comid/testcases/comid-5.cbor b/comid/testcases/comid-5.cbor index f4f514c9..c07f413c 100644 Binary files a/comid/testcases/comid-5.cbor and b/comid/testcases/comid-5.cbor differ diff --git a/comid/testcases/comid-6.cbor b/comid/testcases/comid-6.cbor new file mode 100644 index 00000000..f1ed54fb Binary files /dev/null and b/comid/testcases/comid-6.cbor differ diff --git a/comid/testcases/comid-7.cbor b/comid/testcases/comid-7.cbor new file mode 100644 index 00000000..97be1e1f Binary files /dev/null and b/comid/testcases/comid-7.cbor differ diff --git a/comid/testcases/comid-cend.cbor b/comid/testcases/comid-cend.cbor new file mode 100644 index 00000000..f31c4995 Binary files /dev/null and b/comid/testcases/comid-cend.cbor differ diff --git a/comid/testcases/comid-domain-dep.cbor b/comid/testcases/comid-domain-dep.cbor new file mode 100644 index 00000000..86321d66 Binary files /dev/null and b/comid/testcases/comid-domain-dep.cbor differ diff --git a/comid/testcases/comid-domain-mem.cbor b/comid/testcases/comid-domain-mem.cbor new file mode 100644 index 00000000..29fe0ba3 Binary files /dev/null and b/comid/testcases/comid-domain-mem.cbor differ diff --git a/comid/testcases/comid-flags.cbor b/comid/testcases/comid-flags.cbor new file mode 100644 index 00000000..25d6d81c Binary files /dev/null and b/comid/testcases/comid-flags.cbor differ diff --git a/comid/testcases/comid-integrity-registers.cbor b/comid/testcases/comid-integrity-registers.cbor new file mode 100644 index 00000000..cd58c7a8 Binary files /dev/null and b/comid/testcases/comid-integrity-registers.cbor differ diff --git a/comid/testcases/comid-opaque-instance-id.cbor b/comid/testcases/comid-opaque-instance-id.cbor new file mode 100644 index 00000000..88c1d971 Binary files /dev/null and b/comid/testcases/comid-opaque-instance-id.cbor differ diff --git a/comid/testcases/comid-raw-value.cbor b/comid/testcases/comid-raw-value.cbor new file mode 100644 index 00000000..e7080df6 Binary files /dev/null and b/comid/testcases/comid-raw-value.cbor differ diff --git a/comid/testcases/comid-series.cbor b/comid/testcases/comid-series.cbor new file mode 100644 index 00000000..ca439569 Binary files /dev/null and b/comid/testcases/comid-series.cbor differ diff --git a/comid/testcases/src/comid-1.diag b/comid/testcases/src/comid-1.diag index 759617b5..5efa92ad 100644 --- a/comid/testcases/src/comid-1.diag +++ b/comid/testcases/src/comid-1.diag @@ -30,7 +30,7 @@ / comid.digests / 2 : [ [ / hash-alg-id / 1, / sha256 / / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' - ]] + ] ] } } ] diff --git a/comid/testcases/src/comid-1a.diag b/comid/testcases/src/comid-1a.diag new file mode 100644 index 00000000..50745afb --- /dev/null +++ b/comid/testcases/src/comid-1a.diag @@ -0,0 +1,51 @@ +/ 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 A / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + }, + / measurement-map B / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "2.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'FFaa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-2.diag b/comid/testcases/src/comid-2.diag index f5309a0f..e95f2e2a 100644 --- a/comid/testcases/src/comid-2.diag +++ b/comid/testcases/src/comid-2.diag @@ -8,79 +8,6 @@ / 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 : { @@ -94,11 +21,16 @@ } }, [ - / measurement-map / { - / comid.mval / 1 : { - / comid.svn / 1 : 552(1) - } + / measurement-map A / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1) + } + }, + / measurement-map B / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(2) } + } ] ] ] } diff --git a/comid/testcases/src/comid-2b.diag b/comid/testcases/src/comid-2b.diag new file mode 100644 index 00000000..f5309a0f --- /dev/null +++ b/comid/testcases/src/comid-2b.diag @@ -0,0 +1,105 @@ +/ 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 Root of Trust", + / comid.layer / 3 : 0 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.svn / 1 : 552(1) + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-3.diag b/comid/testcases/src/comid-3.diag index ec0e66d3..759638ae 100644 --- a/comid/testcases/src/comid-3.diag +++ b/comid/testcases/src/comid-3.diag @@ -28,9 +28,41 @@ / hash-alg-id / 6, / sha-256-32 / / hash-value / h'ABCDEF00' ]] } + }, + / measurement-map / { + / comid.mkey / 0: "my_element", + / comid.mval / 1 : { + / comid.digests / 2 : [[ + / hash-alg-id / 6, / sha-256-32 / + / hash-value / h'00FEDCBA' ]] + } + }, + / measurement-map / { + / comid.mkey / 0: 111( h'5502C001' ), + / comid.mval / 1 : { + / comid.digests / 2 : [[ + / hash-alg-id / 6, / sha-256-32 / + / hash-value / h'00FEDCBA' ]] + } + }, + / measurement-map / { + / comid.mkey / 0: 37( h'67b28b6c34cc40a19117ab5b05911e38' ), + / comid.mval / 1 : { + / comid.digests / 2 : [[ + / hash-alg-id / 6, / sha-256-32 / + / hash-value / h'00FEDCBA' ]] + } + }, + / measurement-map / { + / anonymous mkey / + / comid.mval / 1 : { + / comid.digests / 2 : [[ + / hash-alg-id / 6, / sha-256-32 / + / hash-value / h'11223344' ]] + } } ] ] ] } -} +} \ No newline at end of file diff --git a/comid/testcases/src/comid-4.diag b/comid/testcases/src/comid-4.diag index 9c3cd9f4..76f1d284 100644 --- a/comid/testcases/src/comid-4.diag +++ b/comid/testcases/src/comid-4.diag @@ -2,50 +2,34 @@ / 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 A / { - / comid.mval / 1 : { - / comid.ver / 0 : { - / comid.version / 0 : "1.0.0", - / comid.version-scheme / 1 : 16384 / semver / - }, - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' - ] ] - } - }, - / measurement-map B / { - / comid.mval / 1 : { - / comid.ver / 0 : { - / comid.version / 0 : "2.0.0", - / comid.version-scheme / 1 : 16384 / semver / - }, - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'FFaa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' - ] ] - } - } + / reference-triples / 0 : [ + / reference-triple-record / [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner", + / layer / 3 : 1 + } + }, + [ + / measurement-map / { + / mval / 1 : { + / cryptokeys / 13 : [ + / tagged-pkix-base64-key-type / 554("base64_key_ACME_MAX"), + / tagged-pkix-base64-cert-type / 555("base64_cert_ACME_MAX"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_ACME_MAX") + ] + } + } + ] ] - ] ] + ] } -} \ No newline at end of file +} + + \ No newline at end of file diff --git a/comid/testcases/src/comid-5.diag b/comid/testcases/src/comid-5.diag index b9792475..6f7fdc0e 100644 --- a/comid/testcases/src/comid-5.diag +++ b/comid/testcases/src/comid-5.diag @@ -2,52 +2,186 @@ / 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 + / reference-triples / 0 : [ + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( h'67b28b6c34cc40a19117ab5b05911e39' ) + } + }, + [ + / measurement-map / { + / mkey / 0 : "thing 2", + / mval / 1 : { + / cryptokeys / 13 : [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_X"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Y") + ] + } + } + ] + ] + ], + / identity-triples / 2 : [ + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner", + / layer / 3 : 1 + } + }, + / key-list / [ + / tagged-pkix-base64-key-type / 554("base64_key_X"), + / tagged-pkix-base64-cert-type / 555("base64_cert_Y"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Z"), + / tagged-thumbprint-type / 557([ + / alg / 1, / sha256 / + / value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ]), + / tagged-cose-key-type / 558( { 1 : "Key 1" } ), + / tagged-cert-thumbprint-type / 559([ + / alg / 1, / sha256 / + / value / h'55aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ]), + / tagged-cert-path-thumbprint-type / 561([ + / alg / 1, / sha256 / + / value / h'66aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ]) + ] + ], + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( h'67b28b6c34cc40a19117ab5b05911e38' ) + } + }, + / key-list / [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_X"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Y") + ], + / conditions / { + / mkey / 0 : "thing 1" + } + ], + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( h'67b28b6c34cc40a19117ab5b05911e39' ) + } + }, + / key-list / [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_X"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Y") + ], + / conditions / { + / mkey / 0 : "thing 2", + / authorized-by / 1: [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_A"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_B") + ] + } + ], + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( h'67b28b6c34cc40a19117ab5b05911e40' ) + } + }, + / key-list / [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_X"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Y") + ], + / conditions / { + / authorized-by / 1: [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_A"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_B") + ] } - }, - [ - / measurement-map A / { - /comid.mkey / 0 : 1, - / comid.mval / 1 : { - / comid.ver / 0 : { - / comid.version / 0 : "1.0.0", - / comid.version-scheme / 1 : 16384 / semver / - }, - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' - ] ] + ] + ], + / attest-key-triples / 3 : [ + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( + h'67b28b6c34cc40a19117ab5b05911e37' + ), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner", + / layer / 3 : 1 + } + }, + / key-list / [ + / tagged-pkix-base64-key-type / 554("base64_key_X"), + / tagged-pkix-base64-cert-type / 555("base64_cert_Y"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Z") + ] + ], + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( h'67b28b6c34cc40a19117ab5b05911e30' ) + } + }, + / key-list / [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_X"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Y") + ], + / conditions / { + / mkey / 0 : "thing 1" } - }, - / measurement-map B / { - /comid.mkey / 0 : 2, - / comid.mval / 1 : { - / comid.ver / 0 : { - / comid.version / 0 : "2.0.0", - / comid.version-scheme / 1 : 16384 / semver / - }, - / comid.digests / 2 : [ [ - / hash-alg-id / 1, / sha256 / - / hash-value / h'FFaa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' - ] ] + ], + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( h'67b28b6c34cc40a19117ab5b05911e31' ) + } + }, + / key-list / [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_X"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Y") + ], + / conditions / { + / mkey / 0 : "thing 2", + / authorized-by / 1: [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_A"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_B") + ] + } + ], + [ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-uuid-type / 37( h'67b28b6c34cc40a19117ab5b05911e32' ) + } + }, + / key-list / [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_X"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_Y") + ], + / conditions / { + / authorized-by / 1: [ + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_A"), + / tagged-pkix-base64-cert-path-type / 556("base64_cert_path_B") + ] } - } ] - ] ] + ] } -} \ No newline at end of file +} + + diff --git a/comid/testcases/src/comid-6.diag b/comid/testcases/src/comid-6.diag new file mode 100644 index 00000000..9d4d991c --- /dev/null +++ b/comid/testcases/src/comid-6.diag @@ -0,0 +1,31 @@ +/ 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 : / tagged-pkix-base64-key-type / 554("base64_key_X") + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-7.diag b/comid/testcases/src/comid-7.diag new file mode 100644 index 00000000..49092a22 --- /dev/null +++ b/comid/testcases/src/comid-7.diag @@ -0,0 +1,30 @@ +/ concise-mid-tag / { + / comid.tag-identity / 1 : { + / comid.tag-id / 0 : h'3827e03b25dd454cb36a679c923af51f' + }, + / 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 : / tagged-pkix-base64-key-type / 554("base64_key_X") + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.int-range / 15 : 564([/ min: / 1, / max: / null]) + } + }, + / measurement-map / { + / comid.mkey / 0 : 1, + / comid.mval / 1 : { + / comid.int-range / 15 : 564([/ min: / -1, / max: / 1 ]) + } + } + ] + ] ] + } +} diff --git a/comid/testcases/src/comid-cend.diag b/comid/testcases/src/comid-cend.diag new file mode 100644 index 00000000..9b8f4649 --- /dev/null +++ b/comid/testcases/src/comid-cend.diag @@ -0,0 +1,94 @@ +/ 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-triples / 10 : [ + [ + [ + [ / *** stateful-environment-record -1st entry *** / + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-oid-type / 111( + h'5502C000' + ), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner Firmware" + } + }, + [ + { / *** measurement-map *** / + / mval / 1 : / measurement-values-map / { + / ver / 0 : { + / version / 0 : "1.0.0", + / version-scheme / 1 : 16384 / semver / + } + }, + / authorized-by / 2 : [ + / tagged-pkix-base64-key-type / 554("base64_key_X") + ] + } + ] + ], + [ / *** stateful-environment-record -2nd entry *** / + / 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.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] + ] + } + } + ] + ] + ], /*** end stateful-environment-record ***/ + [ /*** endorsements ***/ + [ /*** endorsed-triple-record ***/ + / environment-map / { + / class / 0 : { + / class-id / 0 : + / tagged-oid-type / 111( + h'5502C000' + ), + / vendor / 1 : "ACME Inc.", + / model / 2 : "ACME RoadRunner Firmware" + } + }, + [ + / 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-design-cd.diag b/comid/testcases/src/comid-design-cd.diag index cae08c4c..088fc640 100644 --- a/comid/testcases/src/comid-design-cd.diag +++ b/comid/testcases/src/comid-design-cd.diag @@ -14,116 +14,109 @@ ], / 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 / + / 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 / 5 : h'FFFFFFFF00000000' + / 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 + / 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' + ] ] } - }, - [ - / measurement-map / - { - / comid.mval / 1 : { - / comid.digests / 2 : [ - [ + } + ] + ], + [ + / 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'3FE18ECA4053879E017EF5EB7A3E5157659C5F9BB15B7D09959B8B8647822A4CC21C3AA6721CEF87F5BFA53495DB0833' - ] - ] - } + / 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" + } + }, [ - / 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 : { + / raw-value-group / + / comid.raw-value / 4 : 560(h'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), + / comid.raw-value-mask-DEPRECATED / 5 : h'466224343D681802C1506BBED7D7F00B969BADDD6346E4F2E7CE146692996F22A45814DE81D248F583B65F817B5FCEAB' } - }, - [ - / measurement-map / - { - / comid.mval / 1 : { - / comid.digests / 2 : [ - [ - / hash-alg-id / 7, / SHA384 / - / hash-value / h'20FF681A0882E29B481953888936209CB53DF9C5AAEC606A2C24A0FB138595124B8E3F24A12771BC3854CC68B40361AD' - ] - ] - } - } - ] - ], + } + ] + ] + ], + / comid.endorsed-triples / 1 : [ [ / environment-map / { - / ** Firmware is valid (example assertion) ** / + / ** Design 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 / + / 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'000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'), - / comid.raw-value-mask / 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 / 5 : h'FFFFFFFF00000000' - } + }, + [ + / 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-domain-dep.diag b/comid/testcases/src/comid-domain-dep.diag new file mode 100644 index 00000000..db867f13 --- /dev/null +++ b/comid/testcases/src/comid-domain-dep.diag @@ -0,0 +1,133 @@ + +/ Use case (1) - cyclic trust - NOT PERMITTED / +/ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” has trustee โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” / +/ โ”‚ XYZ_RoT_A โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ XYZ_RoT_B โ”‚ / +/ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ / +/ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” has trustee โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” / +/ โ”‚ XYZ_RoT_B โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ XYZ_RoT_A โ”‚ / +/ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ / + +/ Use case (2) - bootstrapped or transitive trust / +/ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” has trustee โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” / +/ โ”‚ PQC_L1_Loader1 โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ PQC_RoT โ”‚ / +/ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ / +/ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” has trustee โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” / +/ โ”‚ PQC_L1_Loader2 โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ PQC_L1_Loader1 โ”‚ / +/ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ / +/ NOTE: PQC_L1_Loader2 --has transitive trustee --> 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/comid/triples.go b/comid/triples.go index 5f52babf..1ac5d8c2 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 @@ -17,7 +17,10 @@ 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"` + 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 } @@ -26,6 +29,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 +43,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 +86,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 +109,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,10 +130,22 @@ 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 } + if o.DomainMemberships != nil && o.DomainMemberships.IsEmpty() { + o.DomainMemberships = nil + } + + if o.CoswidTriples != nil && o.CoswidTriples.IsEmpty() { + o.CoswidTriples = nil + } + return encoding.SerializeStructToCBOR(em, o) } @@ -123,6 +154,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,10 +175,22 @@ 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 } + if o.DomainMemberships != nil && o.DomainMemberships.IsEmpty() { + o.DomainMemberships = nil + } + + if o.CoswidTriples != nil && o.CoswidTriples.IsEmpty() { + o.CoswidTriples = nil + } + return encoding.SerializeStructToJSON(o) } @@ -215,14 +259,18 @@ func (o *Triples) IterDevIdentityKeys() iter.Seq[*KeyTriple] { } // Valid checks that the Triples is valid as per the specification -func (o Triples) Valid() error { +// nolint:gocyclo +func (o *Triples) Valid() error { // non-empty<> if (o.ReferenceValues == nil || o.ReferenceValues.IsEmpty()) && (o.EndorsedValues == nil || o.EndorsedValues.IsEmpty()) && (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.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") } @@ -260,13 +308,31 @@ 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.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) } } - 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 { @@ -321,6 +387,30 @@ 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 +} + +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 { @@ -333,3 +423,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/comid/triples_test.go b/comid/triples_test.go index 8d5b1544..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,18 +72,18 @@ 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{} 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) { 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/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()) +} diff --git a/corim/cbor.go b/corim/cbor.go index 7b97aa6f..f7dedf49 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 @@ -22,7 +22,8 @@ var ( ComidTag uint64 = 506 corimTagsMap = map[uint64]interface{}{ - 32: comid.TaggedURI(""), + 32: comid.TaggedURI(""), + 111: comid.TaggedOID{}, } ) @@ -45,8 +46,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/corim/example_profile_test.go b/corim/example_profile_test.go index 88bf7024..08842d77 100644 --- a/corim/example_profile_test.go +++ b/corim/example_profile_test.go @@ -12,8 +12,6 @@ import ( "github.com/veraison/corim/comid" "github.com/veraison/corim/extensions" - "github.com/veraison/eat" - "github.com/veraison/swid" ) // ----- profile definition ----- @@ -56,10 +54,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{}). @@ -106,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() @@ -128,10 +123,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 +138,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) } @@ -166,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 @@ -199,5 +191,5 @@ func Example_profile_marshal() { fmt.Printf("corim: %v", hex.EncodeToString(buf)) // output: - // corim: d901f5a30063666f6f0181d901fa58a5a40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026e526f616452756e6e657220322e3081a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 + // corim: d901f5a30063666f6f0181d901fa58a5a40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d8255031fb5abf023e4992aa4e95f9c1503bfa016941434d45204c74642e026e526f616452756e6e657220322e3081a200d8255031fb5abf023e4992aa4e95f9c1503bfa01a10281820644abcdef0003d8207822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 } 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..947dce69 --- /dev/null +++ b/corim/oneormore_test.go @@ -0,0 +1,148 @@ +// 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" +) + +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.NewDigestIntAlg( + comid.Sha256, + comid.MustHexDecode(t, "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef"), + ) + hash2 := comid.NewDigestIntAlg( + comid.Sha256, + comid.MustHexDecode(t, "c0decafec0decafec0decafec0decafec0decafec0decafec0decafec0decafe"), + ) + + digestCases := []struct { + title string + oom OneOrMore[comid.Digest] + expectdCBOR []byte + expectedJSON string + }{ + { + title: "one digest", + oom: OneOrMore[comid.Digest]{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: `[1, "3q2-796tvu_erb7v3q2-796tvu_erb7v3q2-796tvu8"]`, + }, + { + title: "more digests", + oom: OneOrMore[comid.Digest]{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: `[[1, "3q2-796tvu_erb7v3q2-796tvu_erb7v3q2-796tvu8"],[1, "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[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.JSONEq(t, tc.expectedJSON, string(encoded)) + + decoded = OneOrMore[comid.Digest]{} + 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/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 145c11e6..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" ) @@ -39,6 +38,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 @@ -56,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 { @@ -114,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 { @@ -135,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 { @@ -189,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) @@ -209,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) @@ -229,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 { @@ -258,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 { @@ -288,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 } @@ -337,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) } @@ -365,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) @@ -386,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-1.cbor b/corim/testcases/corim-1.cbor new file mode 100644 index 00000000..0cd6ca36 Binary files /dev/null and b/corim/testcases/corim-1.cbor differ diff --git a/corim/testcases/corim-2.cbor b/corim/testcases/corim-2.cbor new file mode 100644 index 00000000..0776dfa7 Binary files /dev/null and b/corim/testcases/corim-2.cbor differ diff --git a/corim/testcases/corim-design-cd.cbor b/corim/testcases/corim-design-cd.cbor new file mode 100644 index 00000000..ba988697 Binary files /dev/null and b/corim/testcases/corim-design-cd.cbor differ 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/corim-firmware-cd.cbor b/corim/testcases/corim-firmware-cd.cbor new file mode 100644 index 00000000..a6209a92 Binary files /dev/null and b/corim/testcases/corim-firmware-cd.cbor differ diff --git a/corim/testcases/corim-roles.cbor b/corim/testcases/corim-roles.cbor new file mode 100644 index 00000000..3724c813 Binary files /dev/null and b/corim/testcases/corim-roles.cbor differ diff --git a/corim/testcases/regen-from-src.sh b/corim/testcases/regen-from-src.sh index abbb4618..3d837ab6 100644 --- a/corim/testcases/regen-from-src.sh +++ b/corim/testcases/regen-from-src.sh @@ -1,10 +1,17 @@ #!/usr/bin/bash -# Copyright 2024 Contributors to the Veraison project. +# Copyright 2024-2026 Contributors to the Veraison project. # SPDX-License-Identifier: Apache-2.0 set -e +THIS_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /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 cd46e64c..7afa5c5d 100644 Binary files a/corim/testcases/signed-corim-with-extensions.cbor and b/corim/testcases/signed-corim-with-extensions.cbor differ diff --git a/corim/testcases/signed-example-corim.cbor b/corim/testcases/signed-example-corim.cbor index 3a17eff5..6ca83ea7 100644 Binary files a/corim/testcases/signed-example-corim.cbor and b/corim/testcases/signed-example-corim.cbor differ diff --git a/corim/testcases/signed-good-corim.cbor b/corim/testcases/signed-good-corim.cbor index d21c888f..5561aec5 100644 Binary files a/corim/testcases/signed-good-corim.cbor and b/corim/testcases/signed-good-corim.cbor differ diff --git a/corim/testcases/src/corim-1.diag b/corim/testcases/src/corim-1.diag new file mode 100644 index 00000000..32bf8ce1 --- /dev/null +++ b/corim/testcases/src/corim-1.diag @@ -0,0 +1,47 @@ +/ 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", + / comid.layer / 3 : 1 + } + }, + [ + / measurement-map / { + / comid.mval / 1 : { + / comid.ver / 0 : { + / comid.version / 0 : "1.0.0", + / comid.version-scheme / 1 : 16384 / semver / + }, + / comid.digests / 2 : [ [ + / hash-alg-id / 1, / sha256 / + / hash-value / h'44aa336af4cb14a879432e53dd6571c7fa9bccafb75f488259262d6ea3a4d91b' + ] ] + } + } + ] + ] ] + } + } + >> ) + ] + } +) 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/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 f6421f79..6d7ef72c 100644 Binary files a/corim/testcases/unsigned-corim-with-extensions.cbor and b/corim/testcases/unsigned-corim-with-extensions.cbor differ diff --git a/corim/testcases/unsigned-example-corim.cbor b/corim/testcases/unsigned-example-corim.cbor index 9c92eaef..26a2e57b 100644 Binary files a/corim/testcases/unsigned-example-corim.cbor and b/corim/testcases/unsigned-example-corim.cbor differ diff --git a/corim/testcases/unsigned-good-corim.cbor b/corim/testcases/unsigned-good-corim.cbor index a9e6ebc0..2df724b5 100644 Binary files a/corim/testcases/unsigned-good-corim.cbor and b/corim/testcases/unsigned-good-corim.cbor differ diff --git a/corim/unsignedcorim.go b/corim/unsignedcorim.go index 6cd32f65..859624b8 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 } @@ -154,11 +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: comid.TaggedURI(href), - Thumbprint: thumbprint, + Href: OneOrMore[comid.TaggedURI]{comid.TaggedURI(href)}, + } + + if thumbprint != nil { + l.Thumbprint = &OneOrMore[comid.Digest]{*thumbprint} } if o.DependentRims == nil { @@ -175,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 } @@ -426,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) } } @@ -570,29 +572,28 @@ 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[comid.Digest] `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 tp := o.Thumbprint; tp != nil { - if err := swid.ValidHashEntry(tp.HashAlgID, tp.HashValue); err != nil { - return fmt.Errorf("invalid locator thumbprint: %w", err) - } + if o.Thumbprint == nil { + return nil } - return nil -} + if err := o.Thumbprint.Valid(); err != nil { + return fmt.Errorf("thumbprint: %w", err) + } -// 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") + for i, tp := range *o.Thumbprint { + if err := tp.Valid(); err != nil { + return fmt.Errorf("invalid locator thumbprint at index %d: %w", i, err) + } } + return nil } diff --git a/corim/unsignedcorim_test.go b/corim/unsignedcorim_test.go index 88ea39d9..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" @@ -358,7 +360,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" + } } ` @@ -429,13 +434,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[comid.Digest]{comid.Digest{}} + assert.EqualError(t, l.Valid(), "invalid locator thumbprint at index 0: zero length value") } @@ -503,9 +508,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()) @@ -524,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) + }) + } +} 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/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/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= 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 8933e23c..7ed8cf1e 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{}) @@ -167,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) } } @@ -195,12 +191,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/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 beba99e1..181d3304 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{}) @@ -176,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") } @@ -191,11 +187,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..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 @@ -62,7 +61,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 @@ -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/cca/testcases/cca-platform-invalid-digests.cbor b/profiles/cca/testcases/cca-platform-invalid-digests.cbor index 4197e270..d406dfed 100644 Binary files a/profiles/cca/testcases/cca-platform-invalid-digests.cbor and b/profiles/cca/testcases/cca-platform-invalid-digests.cbor differ diff --git a/profiles/cca/testcases/cca-platform-invalid-impl-id.cbor b/profiles/cca/testcases/cca-platform-invalid-impl-id.cbor index b0f16755..9fa1116c 100644 Binary files a/profiles/cca/testcases/cca-platform-invalid-impl-id.cbor and b/profiles/cca/testcases/cca-platform-invalid-impl-id.cbor differ diff --git a/profiles/cca/testcases/cca-platform-valid.cbor b/profiles/cca/testcases/cca-platform-valid.cbor index 669513a6..8f7b67cb 100644 Binary files a/profiles/cca/testcases/cca-platform-valid.cbor and b/profiles/cca/testcases/cca-platform-valid.cbor differ diff --git a/profiles/cca/testcases/cca-realm-invalid-missing-rim.cbor b/profiles/cca/testcases/cca-realm-invalid-missing-rim.cbor index 266597ce..76b5191b 100644 Binary files a/profiles/cca/testcases/cca-realm-invalid-missing-rim.cbor and b/profiles/cca/testcases/cca-realm-invalid-missing-rim.cbor differ diff --git a/profiles/cca/testcases/cca-realm-invalid-rim-size.cbor b/profiles/cca/testcases/cca-realm-invalid-rim-size.cbor index 0a75fbdc..cd87828b 100644 Binary files a/profiles/cca/testcases/cca-realm-invalid-rim-size.cbor and b/profiles/cca/testcases/cca-realm-invalid-rim-size.cbor differ diff --git a/profiles/cca/testcases/cca-realm-valid.cbor b/profiles/cca/testcases/cca-realm-valid.cbor index 2f60121a..0b06444c 100644 Binary files a/profiles/cca/testcases/cca-realm-valid.cbor and b/profiles/cca/testcases/cca-realm-valid.cbor differ diff --git a/profiles/cca/testcases/no-profile.cbor b/profiles/cca/testcases/no-profile.cbor index 21f2d10e..ff80a2dd 100644 Binary files a/profiles/cca/testcases/no-profile.cbor and b/profiles/cca/testcases/no-profile.cbor differ diff --git a/profiles/cca/testcases/src/cca-platform-invalid-digests.yaml b/profiles/cca/testcases/src/cca-platform-invalid-digests.yaml index f280631f..a4565a13 100644 --- a/profiles/cca/testcases/src/cca-platform-invalid-digests.yaml +++ b/profiles/cca/testcases/src/cca-platform-invalid-digests.yaml @@ -6,7 +6,9 @@ tag: 501 value: 0: cca-platform-invalid-digests - 3: "tag:arm.com,2025:cca_platform#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:cca_platform#1.0.0" 1: - tag: 506 value: diff --git a/profiles/cca/testcases/src/cca-platform-invalid-impl-id.yaml b/profiles/cca/testcases/src/cca-platform-invalid-impl-id.yaml index 47716145..e8219e19 100644 --- a/profiles/cca/testcases/src/cca-platform-invalid-impl-id.yaml +++ b/profiles/cca/testcases/src/cca-platform-invalid-impl-id.yaml @@ -6,7 +6,9 @@ tag: 501 value: 0: cca-platform-invalid-impl-id - 3: "tag:arm.com,2025:cca_platform#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:cca_platform#1.0.0" 1: - tag: 506 value: diff --git a/profiles/cca/testcases/src/cca-platform-valid.yaml b/profiles/cca/testcases/src/cca-platform-valid.yaml index 8c3f8c85..33f3e3d6 100644 --- a/profiles/cca/testcases/src/cca-platform-valid.yaml +++ b/profiles/cca/testcases/src/cca-platform-valid.yaml @@ -8,7 +8,9 @@ tag: 501 value: 0: cca-platform-valid-corim - 3: "tag:arm.com,2025:cca_platform#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:cca_platform#1.0.0" 1: - tag: 506 value: diff --git a/profiles/cca/testcases/src/cca-realm-invalid-missing-rim.yaml b/profiles/cca/testcases/src/cca-realm-invalid-missing-rim.yaml index a4d2ddb4..55dce80a 100644 --- a/profiles/cca/testcases/src/cca-realm-invalid-missing-rim.yaml +++ b/profiles/cca/testcases/src/cca-realm-invalid-missing-rim.yaml @@ -7,7 +7,9 @@ tag: 501 value: 0: cca-realm-invalid-missing-rim-corim - 3: "tag:arm.com,2025:cca_realm#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:cca_realm#1.0.0" 1: - tag: 506 value: diff --git a/profiles/cca/testcases/src/cca-realm-invalid-rim-size.yaml b/profiles/cca/testcases/src/cca-realm-invalid-rim-size.yaml index 736d8d59..e3c3c6cc 100644 --- a/profiles/cca/testcases/src/cca-realm-invalid-rim-size.yaml +++ b/profiles/cca/testcases/src/cca-realm-invalid-rim-size.yaml @@ -6,7 +6,9 @@ tag: 501 value: 0: cca-realm-invalid-rim-size-corim - 3: "tag:arm.com,2025:cca_realm#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:cca_realm#1.0.0" 1: - tag: 506 value: diff --git a/profiles/cca/testcases/src/cca-realm-valid.yaml b/profiles/cca/testcases/src/cca-realm-valid.yaml index 9106e6da..06e1f926 100644 --- a/profiles/cca/testcases/src/cca-realm-valid.yaml +++ b/profiles/cca/testcases/src/cca-realm-valid.yaml @@ -8,7 +8,9 @@ tag: 501 value: 0: cca-realm-valid-corim - 3: "tag:arm.com,2025:cca_realm#1.0.0" + 3: + tag: 32 + value: "tag:arm.com,2025:cca_realm#1.0.0" 1: - tag: 506 value: diff --git a/profiles/psa/corim_test.go b/profiles/psa/corim_test.go index 27009d72..47059ba7 100644 --- a/profiles/psa/corim_test.go +++ b/profiles/psa/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 getComidFromCorim(t *testing.T, corimData []byte) *comid.Comid { require.Greater(t, len(c.Tags), 0, "CoRIM must have at least one tag") // Get a CoMID with PSA 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, "PSA profile should be registered") diff --git a/profiles/psa/psa.go b/profiles/psa/psa.go index d93db14a..d2625371 100644 --- a/profiles/psa/psa.go +++ b/profiles/psa/psa.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 ProfileURI = "tag:arm.com,2025:psa#1.0.0" @@ -18,10 +17,7 @@ const ProfileURI = "tag:arm.com,2025:psa#1.0.0" const PSASoftwareComponentMkey = "psa.software-component" func init() { - profileID, err := eat.NewProfile(ProfileURI) - if err != nil { - panic(err) - } + profileID := corim.MustNewURIProfile(ProfileURI) mvalExt := &MvalExtensions{} diff --git a/profiles/psa/testcases/no-profile.cbor b/profiles/psa/testcases/no-profile.cbor index 8f56a377..6f3315d3 100644 Binary files a/profiles/psa/testcases/no-profile.cbor and b/profiles/psa/testcases/no-profile.cbor differ diff --git a/profiles/psa/testcases/psa-invalid-attest-key.cbor b/profiles/psa/testcases/psa-invalid-attest-key.cbor index def726c9..36cb2473 100644 Binary files a/profiles/psa/testcases/psa-invalid-attest-key.cbor and b/profiles/psa/testcases/psa-invalid-attest-key.cbor differ diff --git a/profiles/psa/testcases/psa-invalid-impl-id.cbor b/profiles/psa/testcases/psa-invalid-impl-id.cbor index ace0469c..ea236a91 100644 Binary files a/profiles/psa/testcases/psa-invalid-impl-id.cbor and b/profiles/psa/testcases/psa-invalid-impl-id.cbor differ diff --git a/profiles/psa/testcases/psa-valid.cbor b/profiles/psa/testcases/psa-valid.cbor index 2c6102d6..574049b4 100644 Binary files a/profiles/psa/testcases/psa-valid.cbor and b/profiles/psa/testcases/psa-valid.cbor differ 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/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()) } 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..32796ad5 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") @@ -119,14 +115,11 @@ 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() { - 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..6a78caa1 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") @@ -106,14 +102,11 @@ 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() { - 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()) } @@ -164,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() { @@ -213,14 +203,11 @@ 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() { - 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{}). 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"] ] } }