From 1da5fea472f9351b8f50aa9acc36b510ebdde72b Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Fri, 12 Jul 2024 13:18:27 +0100 Subject: [PATCH] feat: implement CoRIM profiles - Add RegisterProfile and UnregisterProfile to associate an extensions.Map with an eat.Profile inside a new Profile structure. - Profile is able to instantiate new signed and unsigned CoRIMs, and CoMIDs with appropriate extensions registered to them. - Add unmarshaling functions that attempt to identify the eat.Profile within the raw data, and retrieve an appropriate Profile, which is then used to get an instance with appropriate extensions registered, which can then be unmarshaled fully. Signed-off-by: Sergei Trofimov --- corim/common_test.go | 115 ++++++ corim/example_profile_test.go | 198 +++++++++++ corim/profiles.go | 326 ++++++++++++++++++ corim/profiles_test.go | 187 ++++++++++ corim/signedcorim.go | 5 + corim/signedcorim_test.go | 4 +- corim/testcases/comid-ext.json | 51 +++ corim/testcases/comid.json | 49 +++ corim/testcases/corim-ext.json | 18 + corim/testcases/corim.json | 16 + corim/testcases/regen-from-src.sh | 30 ++ .../signed-corim-with-extensions.cbor | Bin 0 -> 681 bytes corim/testcases/signed-example-corim.cbor | Bin 0 -> 684 bytes corim/testcases/signed-good-corim.cbor | Bin 0 -> 607 bytes .../testcases/src/corim-with-extensions.yaml | 75 ++++ corim/testcases/src/ec-p256.jwk | 9 + corim/testcases/src/example-corim.yaml | 78 +++++ corim/testcases/src/good-corim.yaml | 66 ++++ corim/testcases/src/meta.yaml | 13 + .../unsigned-corim-with-extensions.cbor | Bin 0 -> 514 bytes corim/testcases/unsigned-example-corim.cbor | Bin 0 -> 517 bytes corim/testcases/unsigned-good-corim.cbor | Bin 0 -> 440 bytes corim/unsignedcorim_test.go | 11 +- 23 files changed, 1241 insertions(+), 10 deletions(-) create mode 100644 corim/common_test.go create mode 100644 corim/example_profile_test.go create mode 100644 corim/profiles.go create mode 100644 corim/profiles_test.go create mode 100644 corim/testcases/comid-ext.json create mode 100644 corim/testcases/comid.json create mode 100644 corim/testcases/corim-ext.json create mode 100644 corim/testcases/corim.json create mode 100644 corim/testcases/regen-from-src.sh create mode 100644 corim/testcases/signed-corim-with-extensions.cbor create mode 100644 corim/testcases/signed-example-corim.cbor create mode 100644 corim/testcases/signed-good-corim.cbor create mode 100644 corim/testcases/src/corim-with-extensions.yaml create mode 100644 corim/testcases/src/ec-p256.jwk create mode 100644 corim/testcases/src/example-corim.yaml create mode 100644 corim/testcases/src/good-corim.yaml create mode 100644 corim/testcases/src/meta.yaml create mode 100644 corim/testcases/unsigned-corim-with-extensions.cbor create mode 100644 corim/testcases/unsigned-example-corim.cbor create mode 100644 corim/testcases/unsigned-good-corim.cbor diff --git a/corim/common_test.go b/corim/common_test.go new file mode 100644 index 00000000..12ebe10d --- /dev/null +++ b/corim/common_test.go @@ -0,0 +1,115 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 + +package corim + +import ( + _ "embed" + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +var ( + // minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate + //go:embed testcases/unsigned-good-corim.cbor + testGoodUnsignedCorimCBOR []byte + + // comid entity and unsigned-corim are extended + //go:embed testcases/unsigned-corim-with-extensions.cbor + testUnsignedCorimWithExtensionsCBOR []byte + + // comid entity and unsigned-corim are extended + //go:embed testcases/signed-good-corim.cbor + testGoodSignedCorimCBOR []byte + + // comid entity and unsigned-corim are extended + //go:embed testcases/signed-corim-with-extensions.cbor + testSignedCorimWithExtensionsCBOR []byte + + //go:embed testcases/corim.json + testUnsignedCorimJSON []byte + + //go:embed testcases/corim-ext.json + testUnsignedCorimWithExtensionsJSON []byte + + //go:embed testcases/comid.json + testComidJSON []byte + + //go:embed testcases/comid-ext.json + testComidWithExtensionsJSON []byte +) + +func assertCoRIMEq(t *testing.T, expected []byte, actual []byte, msgAndArgs ...interface{}) bool { + var expectedCoRIM, actualCoRIM *UnsignedCorim + + if err := dm.Unmarshal(expected, &expectedCoRIM); err != nil { + return assert.Fail(t, fmt.Sprintf( + "Expected value ('%s') is not valid UnsignedCorim: '%s'", + expected, err.Error()), msgAndArgs...) + } + + if err := dm.Unmarshal(actual, &actualCoRIM); err != nil { + return assert.Fail(t, fmt.Sprintf( + "actual value ('%s') is not valid UnsignedCorim: '%s'", + actual, err.Error()), msgAndArgs...) + } + + if !assert.EqualValues(t, expectedCoRIM.ID, actualCoRIM.ID, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.DependentRims, + actualCoRIM.DependentRims, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.Profile, actualCoRIM.Profile, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.RimValidity, + actualCoRIM.RimValidity, msgAndArgs...) { + return false + } + + if !assert.EqualValues(t, expectedCoRIM.Entities, actualCoRIM.Entities, msgAndArgs...) { + return false + } + + if len(expectedCoRIM.Tags) != len(actualCoRIM.Tags) { + allMsgAndArgs := []interface{}{len(expectedCoRIM.Tags), len(actualCoRIM.Tags)} + allMsgAndArgs = append(allMsgAndArgs, msgAndArgs...) + return assert.Fail(t, fmt.Sprintf( + "Unexpected number of Tags: expected %d, actual %d", allMsgAndArgs...)) + } + + for i, expectedTag := range expectedCoRIM.Tags { + actualTag := actualCoRIM.Tags[i] + + if !assertCBOREq(t, expectedTag, actualTag, msgAndArgs...) { + return false + } + } + + return true +} + +func assertCBOREq(t *testing.T, expected []byte, actual []byte, msgAndArgs ...interface{}) bool { + var expectedCBOR, actualCBOR interface{} + + if err := dm.Unmarshal(expected, &expectedCBOR); err != nil { + return assert.Fail(t, fmt.Sprintf( + "Expected value ('%s') is not valid cbor.\nCBOR parsing error: '%s'", + expected, err.Error()), msgAndArgs...) + } + + if err := dm.Unmarshal(actual, &actualCBOR); err != nil { + return assert.Fail(t, fmt.Sprintf( + "Input ('%s') needs to be valid cbor.\nCBOR parsing error: '%s'", + actual, err.Error()), msgAndArgs...) + } + + return assert.Equal(t, expectedCBOR, actualCBOR, msgAndArgs...) +} diff --git a/corim/example_profile_test.go b/corim/example_profile_test.go new file mode 100644 index 00000000..435ba476 --- /dev/null +++ b/corim/example_profile_test.go @@ -0,0 +1,198 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "encoding/hex" + "errors" + "fmt" + "log" + "os" + "time" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" + "github.com/veraison/swid" +) + +// ----- profile definition ----- +// The following code defines a profile with the following extensions and +// constraints: +// - Entities may contain an address field +// - Reference values may contain a Unix timestamp indicating when the +// individual measurement was taken. +// - The language claim (CoMID index 0) must be present, and must be "en-GB" + +// These extensions will be used for both CoMID and CoRIM entities. +type EntityExtensions struct { + Address *string `cbor:"-1,keyasint,omitempty" json:"address,omitempty"` +} + +type RefValExtensions struct { + Timestamp *int `cbor:"-1,keyasint,omitempty" json:"timestamp,omitempty"` +} + +// We're not defining any additional fields, however we're providing extra +// constraints that will be applied on top of standard CoMID validation. +type ComidExtensions struct{} + +func (*ComidExtensions) ConstrainComid(c *comid.Comid) error { + if c.Language == nil { + return errors.New("language not specified") + } + + if *c.Language != "en-GB" { + return fmt.Errorf(`language must be "en-GB", but found %q`, *c.Language) + } + + return nil +} + +// 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 +// remember to register it at the time you want to use it. The only potential +// danger with that is if the your profile ID clashes with another profile, +// 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 + } + + extMap := extensions.NewMap(). + Add(ExtEntity, &EntityExtensions{}). + Add(comid.ExtComid, &ComidExtensions{}). + Add(comid.ExtEntity, &EntityExtensions{}). + Add(comid.ExtReferenceValue, &RefValExtensions{}) + + if err := RegisterProfile(profileID, extMap); err != nil { + panic(err) // will not error, assuming our profile ID is unique, + // and we've correctly set up the extensions Map above + } +} + +// ----- end of profile definition ----- +// The following code demonstrates how the profile might be used. + +func Example_profile_unmarshal() { + buf, err := os.ReadFile("testcases/unsigned-example-corim.cbor") + if err != nil { + log.Fatalf("could not read corim file: %v", err) + } + + // UnmarshalUnsignedCorimFromCBOR will detect the profile and ensure + // the correct extensions are loaded before unmarshalling + extractedCorim, err := UnmarshalUnsignedCorimFromCBOR(buf) + if err != nil { + log.Fatalf("could not unmarshal corim: %v", err) + } + + extractedComid, err := UnmarshalComidFromCBOR( + extractedCorim.Tags[0], + extractedCorim.Profile, + ) + if err != nil { + log.Fatalf("could not unmarshal corim: %v", err) + } + + fmt.Printf("Language: %s\n", *extractedComid.Language) + fmt.Printf("Entity: %s\n", *extractedComid.Entities.Values[0].EntityName) + fmt.Printf(" %s\n", extractedComid.Entities.Values[0]. + Extensions.MustGetString("Address")) + + fmt.Printf("Measurements:\n") + for _, m := range extractedComid.Triples.ReferenceValues.Values[0].Measurements.Values { + + val := hex.EncodeToString((*m.Val.Digests)[0].HashValue) + tsInt := m.Val.Extensions.MustGetInt64("timestamp") + ts := time.Unix(tsInt, 0).UTC() + + fmt.Printf(" %v taken at %s\n", val, ts.Format("2006-01-02T15:04:05")) + } + + // output: + // Language: en-GB + // Entity: ACME Ltd. + // 123 Fake Street + // Measurements: + // 87428fc522803d31065e7bce3cf03fe475096631e5e07bbd7a0fde60c4cf25c7 taken at 2024-07-12T11:03:10 + // 0263829989b6fd954f72baaf2fc64bc2e2f01d692d4de72986ea808f6e99813f taken at 2024-07-12T11:03:10 + // a3a5e715f0cc574a73c3f9bebb6bc24f32ffd5b67b387244c2c909da779a1478 taken at 2024-07-12T11:03:10 +} + +// note: this example is rather verbose as we're going to be constructing a +// CoMID by hand. In practice, you would typically write a JSON document and +// 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) + } + + profile, ok := GetProfile(profileID) + if !ok { + log.Fatalf("profile %v not found", profileID) + } + + myCorim := profile.GetUnsignedCorim() + myComid := profile.GetComid(). + SetLanguage("en-GB"). + SetTagIdentity("example", 0). + // Adding an entity to the Entities collection also registers + // profile's extensions + AddEntity("ACME Ltd.", &comid.TestRegID, comid.RoleCreator) + + address := "123 Fake Street" + err = myComid.Entities.Values[0].Extensions.Set("Address", &address) + if err != nil { + log.Fatalf("could not set entity Address: %v", err) + } + + refVal := comid.ValueTriple{ + Environment: comid.Environment{ + Class: comid.NewClassImplID(comid.TestImplID). + SetVendor("ACME Ltd."). + SetModel("RoadRunner 2.0"), + }, + Measurements: *comid.NewMeasurements(), + } + + measurement := comid.MustNewPSAMeasurement( + comid.MustCreatePSARefValID( + comid.TestSignerID, "BL", "5.0.5", + )).AddDigest(swid.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 + // extension directly to the measurement, we're using a different + // extension point, comid.ExtMval rather than comid.ExtReferenceValue, + // as a measurement doesn't know that its going to be part of reference + // value, ans so is unaware of reference value extension points. + extMap := extensions.NewMap().Add(comid.ExtMval, &RefValExtensions{}) + if err = measurement.Val.RegisterExtensions(extMap); err != nil { + log.Fatal("could not register refval extensions") + } + + refVal.Measurements.Add(measurement) + myComid.Triples.AddReferenceValue(refVal) + + err = myComid.Valid() + if err != nil { + log.Fatalf("comid validity: %v", err) + } + + myCorim.AddComid(*myComid) + + buf, err := myCorim.ToCBOR() + if err != nil { + log.Fatalf("could not encode CoRIM: %v", err) + } + + fmt.Printf("corim: %v", hex.EncodeToString(buf)) + + // output: + // corim: a300f6018158d9d901faa40065656e2d474201a100676578616d706c650281a4006941434d45204c74642e01d8207468747470733a2f2f61636d652e6578616d706c65028101206f3132332046616b652053747265657404a1008182a100a300d90258582061636d652d696d706c656d656e746174696f6e2d69642d303030303030303031016941434d45204c74642e026e526f616452756e6e657220322e3081a200d90259a30162424c0465352e302e35055820acbb11c7e4da217205523ce4ce1a245ae1a239ae3c6bfd9e7871f7e5d8bae86b01a10281820644abcdef00037822687474703a2f2f6578616d706c652e636f6d2f6578616d706c652d70726f66696c65 +} diff --git a/corim/profiles.go b/corim/profiles.go new file mode 100644 index 00000000..7a0f51e7 --- /dev/null +++ b/corim/profiles.go @@ -0,0 +1,326 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "encoding/json" + "fmt" + "reflect" + + "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" + "github.com/veraison/go-cose" +) + +// SignedCorimMapExtensionPoints is a list of extension.Point's valid for a +// SignedCorim. +var SignedCorimMapExtensionPoints = []extensions.Point{ + ExtSigner, + ExtUnsignedCorim, + ExtEntity, +} + +// UnsignedCorimMapExtensionPoints is a list of extension.Point's valid for a +// UnsignedCorim. +var UnsignedCorimMapExtensionPoints = []extensions.Point{ + ExtUnsignedCorim, + ExtEntity, +} + +// ComidMapExtensionPoints is a list of extension.Point's valid for a comid.Comid. +var ComidMapExtensionPoints = []extensions.Point{ + comid.ExtComid, + comid.ExtEntity, + comid.ExtTriples, + comid.ExtReferenceValue, + comid.ExtReferenceValueFlags, + comid.ExtEndorsedValue, + comid.ExtEndorsedValueFlags, +} + +// AllExtensionPoints is a list of all valid extension.Point's +var AllExtensionPoints = make(map[extensions.Point]bool) // populated inside init() below + +// UnmarshalSignedCorimFromCBOR unmarshals a SignedCorim from provided +// CBOR data. If there are extensions associated with the profile specified by +// the data, they will be registered with the UnsignedCorim before it is +// unmarshaled. +func UnmarshalSignedCorimFromCBOR(buf []byte) (*SignedCorim, error) { + message := cose.NewSign1Message() + + if err := message.UnmarshalCBOR(buf); err != nil { + return nil, fmt.Errorf("failed CBOR decoding for COSE-Sign1 signed CoRIM: %w", err) + } + + profiled := struct { + Profile *eat.Profile `cbor:"3,keyasint,omitempty"` + }{} + + if err := dm.Unmarshal(message.Payload, &profiled); err != nil { + return nil, err + } + + ret := GetSignedCorim(profiled.Profile) + if err := ret.FromCOSE(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// UnmarshalUnsignedCorimFromCBOR unmarshals an UnsignedCorim from provided +// CBOR data. If there are extensions associated with the profile specified by +// the data, they will be registered with the UnsignedCorim before it is +// unmarshaled. +func UnmarshalUnsignedCorimFromCBOR(buf []byte) (*UnsignedCorim, error) { + profiled := struct { + Profile *eat.Profile `cbor:"3,keyasint,omitempty"` + }{} + + if err := dm.Unmarshal(buf, &profiled); err != nil { + return nil, err + } + + ret := GetUnsignedCorim(profiled.Profile) + if err := ret.FromCBOR(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// UnmarshalUnsignedCorimFromJSON unmarshals an UnsignedCorim from provided +// JSON data. If there are extensions associated with the profile specified by +// the data, they will be registered with the UnsignedCorim before it is +// unmarshaled. +func UnmarshalUnsignedCorimFromJSON(buf []byte) (*UnsignedCorim, error) { + profiled := struct { + Profile *eat.Profile `cbor:"3,keyasint,omitempty"` + }{} + + if err := json.Unmarshal(buf, &profiled); err != nil { + return nil, err + } + + ret := GetUnsignedCorim(profiled.Profile) + if err := ret.FromJSON(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// 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) { + var ret *comid.Comid + + profile, ok := GetProfile(profileID) + if ok { + ret = profile.GetComid() + } else { + ret = comid.NewComid() + } + + if err := ret.FromCBOR(buf); err != nil { + return nil, err + } + + return ret, nil +} + +// 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 { + var ret *SignedCorim + + if profileID == nil { + ret = NewSignedCorim() + } else { + profile, ok := GetProfile(profileID) + if !ok { + // unknown profile -- treat here like an unprofiled + // CoRIM. While the CoRIM spec states that unknown + // profiles should be rejected, we're not actually + // validating the profile here, just trying to identify + // any extensions we may need to load. Profile + // validation is left up to the calling code, as a + // profile only needs to be registered here if it + // defines extensions. Profiles that do not add any + // additional fields may not be registered. + ret = NewSignedCorim() + } else { + ret = profile.GetSignedCorim() + } + } + + return ret +} + +// 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 { + var ret *UnsignedCorim + + if profileID == nil { + ret = NewUnsignedCorim() + } else { + profile, ok := GetProfile(profileID) + if !ok { + // unknown profile -- treat here like an unprofiled + // CoRIM. While the CoRIM spec states that unknown + // profiles should be rejected, we're not actually + // validating the profile here, just trying to identify + // any extensions we may need to load. Profile + // validation is left up to the calling code, as a + // profile only needs to be registered here if it + // defines extensions. Profiles that do not add any + // additional fields may not be registered. + ret = NewUnsignedCorim() + } else { + ret = profile.GetUnsignedCorim() + } + } + + return ret +} + +// Profile associates an EAT profile ID with a set of extensions. It allows +// obtaining new CoRIM and CoMID structures that had associated extensions +// registered. +type Profile struct { + ID *eat.Profile + MapExtensions extensions.Map +} + +// GetComid returns a pointer to a new comid.Comid that had the Profile's +// extensions (if any) registered. +func (o *Profile) GetComid() *comid.Comid { + ret := comid.NewComid() + o.registerExtensions(ret, ComidMapExtensionPoints) + return ret +} + +// GetUnsignedCorim returns a pointer to a new UnsignedCorim that had the +// Profile's extensions (if any) registered. +func (o *Profile) GetUnsignedCorim() *UnsignedCorim { + ret := NewUnsignedCorim() + ret.Profile = o.ID + o.registerExtensions(ret, UnsignedCorimMapExtensionPoints) + return ret +} + +// GetSignedCorim returns a pointer to a new SignedCorim that had the +// Profile's extensions (if any) registered. +func (o *Profile) GetSignedCorim() *SignedCorim { + ret := NewSignedCorim() + ret.UnsignedCorim.Profile = o.ID + o.registerExtensions(ret, SignedCorimMapExtensionPoints) + return ret +} + +func (o *Profile) registerExtensions(e iextensible, points []extensions.Point) { + exts := extensions.NewMap() + for _, p := range points { + if v, ok := o.MapExtensions[p]; ok { + exts[p] = v + } + } + + if err := e.RegisterExtensions(exts); err != nil { + // exts is a subset of o.MapExtensions which have been + // validated when the profile was registered, so we should never + // get here. + panic(err) + } +} + +// 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 { + return err + } + + if _, ok := profilesRegister[strID]; ok { + return fmt.Errorf("profile with id %q already registered", strID) + } + + for p, v := range exts { + if _, ok := AllExtensionPoints[p]; !ok { + return fmt.Errorf("%w: %q", extensions.ErrUnexpectedPoint, p) + } + + if reflect.TypeOf(v).Kind() != reflect.Pointer { + return fmt.Errorf("attempting to register a non-pointer IValue for %q", p) + } + } + + profilesRegister[strID] = Profile{ID: id, MapExtensions: exts} + + return nil +} + +// 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 { + return false + } + + strID, err := id.Get() + if err != nil { + return false + } + + if _, ok := profilesRegister[strID]; ok { + delete(profilesRegister, strID) + return true + } + + return false +} + +// GetProfile returns the Profile associated with the specified ID, or an empty +// profile if no Profile has been registered for the id. The second return +// value indicates whether a profile for the ID has been found. +func GetProfile(id *eat.Profile) (Profile, bool) { + if id == nil { + return Profile{}, false + } + + strID, err := id.Get() + if err != nil { + return Profile{}, false + } + + prof, ok := profilesRegister[strID] + return prof, ok +} + +type iextensible interface { + RegisterExtensions(exts extensions.Map) error +} + +var profilesRegister = make(map[string]Profile) + +func init() { + for _, p := range SignedCorimMapExtensionPoints { + AllExtensionPoints[p] = true + } + + for _, p := range UnsignedCorimMapExtensionPoints { + AllExtensionPoints[p] = true + } + + for _, p := range ComidMapExtensionPoints { + AllExtensionPoints[p] = true + } +} diff --git a/corim/profiles_test.go b/corim/profiles_test.go new file mode 100644 index 00000000..c8f41aab --- /dev/null +++ b/corim/profiles_test.go @@ -0,0 +1,187 @@ +// Copyright 2024 Contributors to the Veraison project. +// SPDX-License-Identifier: Apache-2.0 +package corim + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/veraison/corim/comid" + "github.com/veraison/corim/extensions" + "github.com/veraison/eat" +) + +func TestProfile_registration(t *testing.T) { + exts := extensions.NewMap() + + err := RegisterProfile(&eat.Profile{}, exts) + assert.EqualError(t, err, "no valid EAT profile") + + p1, err := eat.NewProfile("1.2.3") + require.NoError(t, err) + + err = RegisterProfile(p1, exts) + assert.NoError(t, err) + + p2, err := eat.NewProfile("1.2.3") + require.NoError(t, err) + + err = RegisterProfile(p2, exts) + assert.EqualError(t, err, `profile with id "1.2.3" already registered`) + + ret := UnregisterProfile(p2) + assert.True(t, ret) + ret = UnregisterProfile(p2) + assert.False(t, ret) + ret = UnregisterProfile(nil) + assert.False(t, ret) + + err = RegisterProfile(p2, exts) + assert.NoError(t, err) + + prof, ok := GetProfile(p1) + assert.True(t, ok) + assert.Equal(t, exts, prof.MapExtensions) + + _, ok = GetProfile(&eat.Profile{}) + assert.False(t, ok) + + p3, err := eat.NewProfile("2.3.4") + require.NoError(t, err) + + exts2 := extensions.NewMap().Add(extensions.Point("test"), &struct{}{}) + err = RegisterProfile(p3, exts2) + assert.EqualError(t, err, `unexpected extension point: "test"`) + + exts3 := extensions.NewMap().Add(ExtEntity, struct{}{}) + err = RegisterProfile(p3, exts3) + assert.EqualError(t, err, `attempting to register a non-pointer IValue for "CorimEntity"`) + + UnregisterProfile(p1) +} + +func TestProfile_getters(t *testing.T) { + id, err := eat.NewProfile("1.2.3") + require.NoError(t, err) + + profile := Profile{ + ID: id, + MapExtensions: extensions.NewMap(). + Add(comid.ExtComid, &struct{}{}). + Add(ExtUnsignedCorim, &struct{}{}). + Add(ExtSigner, &struct{}{}), + } + + c := profile.GetComid() + assert.NotNil(t, c.Extensions.IValue) + + u := profile.GetUnsignedCorim() + assert.NotNil(t, u.Extensions.IValue) + + s := profile.GetSignedCorim() + assert.NotNil(t, s.UnsignedCorim.Extensions.IValue) + assert.NotNil(t, s.Meta.Signer.Extensions.IValue) +} + +func TestProfile_marshaling(t *testing.T) { + type corimExtensions struct { + Extension1 *string `cbor:"-1,keyasint,omitempty" json:"ext1,omitempty"` + } + + type entityExtensions struct { + Address *string `cbor:"-1,keyasint,omitempty" json:"address,omitempty"` + } + + type refValExtensions struct { + Timestamp *int `cbor:"-1,keyasint,omitempty" json:"timestamp,omitempty"` + } + + profID, err := eat.NewProfile("http://example.com/test-profile") + require.NoError(t, err) + + extMap := extensions.NewMap(). + Add(ExtUnsignedCorim, &corimExtensions{}). + Add(comid.ExtEntity, &entityExtensions{}). + Add(comid.ExtReferenceValue, &refValExtensions{}) + err = RegisterProfile(profID, extMap) + require.NoError(t, err) + + c, err := UnmarshalUnsignedCorimFromCBOR(testGoodUnsignedCorimCBOR) + assert.NoError(t, err) + assert.Nil(t, c.Profile) + + c, err = UnmarshalUnsignedCorimFromCBOR(testUnsignedCorimWithExtensionsCBOR) + assert.NoError(t, err) + + assert.Equal(t, profID, c.Profile) + assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) + + profile, ok := GetProfile(c.Profile) + assert.True(t, ok) + + cmd, err := UnmarshalComidFromCBOR(c.Tags[0], c.Profile) + assert.NoError(t, err) + + address := cmd.Entities.Values[0].Extensions.MustGetString("Address") + assert.Equal(t, "123 Fake Street", address) + + ts := cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. + Val.Extensions.MustGetInt("timestamp") + assert.Equal(t, 1720782190, ts) + + unregProfID, err := eat.NewProfile("http://example.com") + require.NoError(t, err) + + cmdNoExt, err := UnmarshalComidFromCBOR(c.Tags[0], unregProfID) + assert.NoError(t, err) + + address = cmdNoExt.Entities.Values[0].Extensions.MustGetString("Address") + assert.Equal(t, "", address) + + out, err := c.ToCBOR() + assert.NoError(t, err) + assertCoRIMEq(t, testUnsignedCorimWithExtensionsCBOR, out) + + out, err = cmd.ToCBOR() + assert.NoError(t, err) + // the first 3 bytes in Tags[0] is the tag indicating CoRIM + assertCBOREq(t, c.Tags[0][3:], out) + + c, err = UnmarshalUnsignedCorimFromJSON(testUnsignedCorimJSON) + assert.NoError(t, err) + assert.Nil(t, c.Profile) + + c, err = UnmarshalUnsignedCorimFromJSON(testUnsignedCorimWithExtensionsJSON) + assert.NoError(t, err) + + assert.Equal(t, profID, c.Profile) + assert.Equal(t, "foo", c.Extensions.MustGetString("Extension1")) + + cmd = profile.GetComid() + err = cmd.FromJSON(testComidJSON) + assert.NoError(t, err) + + cmd = profile.GetComid() + err = cmd.FromJSON(testComidWithExtensionsJSON) + assert.NoError(t, err) + + address = cmd.Entities.Values[0].Extensions.MustGetString("Address") + assert.Equal(t, "123 Fake Street", address) + + ts = cmd.Triples.ReferenceValues.Values[0].Measurements.Values[0]. + Val.Extensions.MustGetInt("timestamp") + assert.Equal(t, 1720782190, ts) + + s, err := UnmarshalSignedCorimFromCBOR(testGoodSignedCorimCBOR) + assert.NoError(t, err) + assert.Nil(t, s.UnsignedCorim.Profile) + + s, err = UnmarshalSignedCorimFromCBOR(testSignedCorimWithExtensionsCBOR) + assert.NoError(t, err) + + assert.Equal(t, profID, s.UnsignedCorim.Profile) + assert.Equal(t, "foo", s.UnsignedCorim.Extensions.MustGetString("Extension1")) + + UnregisterProfile(profID) +} diff --git a/corim/signedcorim.go b/corim/signedcorim.go index 32d2acfc..3d7b2158 100644 --- a/corim/signedcorim.go +++ b/corim/signedcorim.go @@ -28,6 +28,11 @@ type SignedCorim struct { message *cose.Sign1Message } +// NewSignedCorim instantiates an empty SignedCorim +func NewSignedCorim() *SignedCorim { + return &SignedCorim{} +} + func (o *SignedCorim) RegisterExtensions(exts extensions.Map) error { unsignedExts := extensions.NewMap() diff --git a/corim/signedcorim_test.go b/corim/signedcorim_test.go index e6ba9e8d..290555e2 100644 --- a/corim/signedcorim_test.go +++ b/corim/signedcorim_test.go @@ -346,7 +346,7 @@ func TestSignedCorim_SignVerify_ok(t *testing.T) { var SignedCorimIn SignedCorim - SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorim) + SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) SignedCorimIn.Meta = *metaGood(t) cbor, err := SignedCorimIn.Sign(signer) @@ -373,7 +373,7 @@ func TestSignedCorim_SignVerify_fail_tampered(t *testing.T) { var SignedCorimIn SignedCorim - SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorim) + SignedCorimIn.UnsignedCorim = *unsignedCorimFromCBOR(t, testGoodUnsignedCorimCBOR) cbor, err := SignedCorimIn.Sign(signer) assert.Nil(t, err) diff --git a/corim/testcases/comid-ext.json b/corim/testcases/comid-ext.json new file mode 100644 index 00000000..541113ec --- /dev/null +++ b/corim/testcases/comid-ext.json @@ -0,0 +1,51 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "address": "123 Fake Street", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurements": [ + { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "timestamp": 1720782190, + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } + ] + } + ] + } +} + diff --git a/corim/testcases/comid.json b/corim/testcases/comid.json new file mode 100644 index 00000000..974a473b --- /dev/null +++ b/corim/testcases/comid.json @@ -0,0 +1,49 @@ +{ + "lang": "en-GB", + "tag-identity": { + "id": "43BBE37F-2E61-4B33-AED3-53CFF1428B16", + "version": 0 + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "https://acme.example", + "roles": [ + "tagCreator", + "creator", + "maintainer" + ] + } + ], + "triples": { + "reference-values": [ + { + "environment": { + "class": { + "id": { + "type": "psa.impl-id", + "value": "YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE=" + }, + "vendor": "ACME", + "model": "RoadRunner" + } + }, + "measurements": [ + { + "key": { + "type": "cca.platform-config-id", + "value": "cfg v1.0.0" + }, + "value": { + "raw-value": { + "type": "bytes", + "value": "cmF3dmFsdWUKcmF3dmFsdWUK" + } + } + } + ] + } + ] + } +} + diff --git a/corim/testcases/corim-ext.json b/corim/testcases/corim-ext.json new file mode 100644 index 00000000..a04fbe7c --- /dev/null +++ b/corim/testcases/corim-ext.json @@ -0,0 +1,18 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "profile": "http://example.com/test-profile", + "ext1": "foo", + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "acme.example", + "roles": [ + "manifestCreator" + ] + } + ] +} diff --git a/corim/testcases/corim.json b/corim/testcases/corim.json new file mode 100644 index 00000000..dfdc9801 --- /dev/null +++ b/corim/testcases/corim.json @@ -0,0 +1,16 @@ +{ + "corim-id": "5c57e8f4-46cd-421b-91c9-08cf93e13cfc", + "validity": { + "not-before": "2021-12-31T00:00:00Z", + "not-after": "2025-12-31T00:00:00Z" + }, + "entities": [ + { + "name": "ACME Ltd.", + "regid": "acme.example", + "roles": [ + "manifestCreator" + ] + } + ] +} diff --git a/corim/testcases/regen-from-src.sh b/corim/testcases/regen-from-src.sh new file mode 100644 index 00000000..5d6501f2 --- /dev/null +++ b/corim/testcases/regen-from-src.sh @@ -0,0 +1,30 @@ +#!/usr/bin/bash +# Copyright 2024 Contributors to the Veraison project. +# SPDX-License-Identifier: Apache-2.0 +set -e + +GEN_TESTCASE=$(go env GOPATH)/bin/gen-testcase + +if [[ ! -f ${GEN_TESTCASE} ]]; then + echo "installing gen-testcase" + go install github.com/setrofim/gen-testcase@v0.0.2 +fi + +testcases=( + good-corim + example-corim + corim-with-extensions +) + +for case in "${testcases[@]}"; do + echo "generating unsigned-${case}.cbor" + + ${GEN_TESTCASE} "src/${case}.yaml" -o "unsigned-${case}.cbor" + + echo "generating signed-${case}.cbor" + + ${GEN_TESTCASE} -s src/ec-p256.jwk -m src/meta.yaml "src/${case}.yaml" \ + -o "signed-${case}.cbor" +done + +echo "done." diff --git a/corim/testcases/signed-corim-with-extensions.cbor b/corim/testcases/signed-corim-with-extensions.cbor new file mode 100644 index 0000000000000000000000000000000000000000..4bfca682959be82d3ace80585e5e20241a5f7759 GIT binary patch literal 681 zcmccA5)r$YQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0Bf@bJ<06KGQi zX$f$Q!@i2lS00sk+FhXVs@%Ra7j^WY6(Lo%vpLY z3mF=l7BVbmxXBa|p#XBbZYId(xv6<2V4v$|rsx_#fgxiGNF!5LP<~=cP-$LXYEknd zpw7s}j7d&DEU89%hI$695ejQ|3m$)ROR9&D8aMuG#jgp+9eCqdi19*gY(%hI+<&ro^}=XUVoX%7v{`;#_A{lU=b7` literal 0 HcmV?d00001 diff --git a/corim/testcases/signed-example-corim.cbor b/corim/testcases/signed-example-corim.cbor new file mode 100644 index 0000000000000000000000000000000000000000..f6c4f46185582023dc04983cbef0118c99ed3c42 GIT binary patch literal 684 zcmccA5)r$YQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0Bf@bJ!y<+fM`vGG1)q`>h2qTg zyv)3Gh3wQy#v2MH86_nJ#a88bl^K%Wupp9& zbqPamNosM4LUKOPWQEKW<_aZ{R-jI(CcWhRTo_xopeR2rGbdFcIW0e*u`!bI*iFV? zOPCs$;IfganSqf>A>Yu*SivnZJ5?dLq$oADgdr2=YCV>P42?|-85T3#WQvGT06A7S z6Xev~)Vva~hjlYkbPb@ukTC_Mktr)EKQSe!G%qi;sCf}kXXIkm2!%Df1&=?urC7uo zWb@>lluFdYMV9Mqvj5JjDE$8P#;zCHj7d&DEU89%hI$5!i4WOMRs8o7j+|FLt{{~*kv9)w17k`+P<{wYs-d2-o+&ZbLG55l zZkpM-?eA3oqFw9tk9i+@^g%XL*Y~+*+pC8DyqS&msCGDl?Jxj3(u4#%7B77+`r%Bt zSMlMW`*vp^@;CZ_bz8MXk;|czoVUtniBv%BU`S2P({*=ZT*wgMy!&yzUZS`0y34`m zKRR`bML67UZIrI>+WGeOt_ATkvQwu|i(?E@^bHWpEwP^LVEwLUd$-}~*C7!}=gRq3 ahkUZzmiM%D(}vv^il;v9vXJ3fUIGAX$P||V literal 0 HcmV?d00001 diff --git a/corim/testcases/signed-good-corim.cbor b/corim/testcases/signed-good-corim.cbor new file mode 100644 index 0000000000000000000000000000000000000000..50dae69118fff2b0e11127df304879307902dcb1 GIT binary patch literal 607 zcmccA5)r$YQH{AIv7jI)GdZy&Ge1wiC^J_(IVr!0Bf@bJ<06KGQiKQODWNK_;j8JHI>OZQ~U~9-0SAEXrgZ-0I z&NRcP532W8@!v~0a$fZ~#B9ctfS~*kmQ+JMV?9%1EP|TOl-xA4bKBpk{zbdi>mTzz z^yq_ZrmpXE&9+w!{dqGR?O~=nf=xF7I?RLw(-$v&F8bk2xL5JvpZj)aAM!W)e|1~6 zMUl&)lbpB8XNgoWq^9QSx;rs0WC(EH{kUE)(c5_4<>2!low~)C8W%G)GcYnSWWu6R z4_DkpIIL%RX??cHU7;?_?op6-M5RZ%RBV@;^u?8i&%EO;W4nYG@+VsK^0K69@7p-{ YZ_LhL%-gg3Zp8k+wv*8@y|VuS06A{>k^lez literal 0 HcmV?d00001 diff --git a/corim/testcases/src/corim-with-extensions.yaml b/corim/testcases/src/corim-with-extensions.yaml new file mode 100644 index 00000000..2f1a45f1 --- /dev/null +++ b/corim/testcases/src/corim-with-extensions.yaml @@ -0,0 +1,75 @@ +# minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate +# - profile (claim 3) has been set to "http://example.com/test-profile" +# - extension claim -1 has been added to corim +# - extension claim -1 has been added to comid entity +--- +0: test corim id +3: http://example.com/test-profile +-1: foo +1: +- encodedCBOR: + tag: 506 + value: + 0: en-GB + 1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== + 2: + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + -1: 123 Fake Street + 4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 + diff --git a/corim/testcases/src/ec-p256.jwk b/corim/testcases/src/ec-p256.jwk new file mode 100644 index 00000000..e3c07719 --- /dev/null +++ b/corim/testcases/src/ec-p256.jwk @@ -0,0 +1,9 @@ +{ + "kty": "EC", + "crv": "P-256", + "x": "MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4", + "y": "4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM", + "d": "870MB6gfuTJ4HtUnUvYMyJpr5eUZNP4Bk43bVdj3eAE", + "use": "enc", + "kid": "1" +} diff --git a/corim/testcases/src/example-corim.yaml b/corim/testcases/src/example-corim.yaml new file mode 100644 index 00000000..55d45847 --- /dev/null +++ b/corim/testcases/src/example-corim.yaml @@ -0,0 +1,78 @@ +# minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate +# - profile (claim 3) has been set to "http://example.com/example-profile" +# - extension claim -1 has been added to corim +# - extension claim -1 has been added to comid entity +# NOTE: this is the as corim-with-extensions.yaml, except for the profile +# string, which is differentiated so that the example (which uses init() +# to register the profile) doesn't clash with the tests. +--- +0: test corim id +3: http://example.com/example-profile +-1: foo +1: +- encodedCBOR: + tag: 506 + value: + 0: en-GB + 1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== + 2: + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + -1: 123 Fake Street + 4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + -1: 1720782190 + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + -1: 1720782190 + diff --git a/corim/testcases/src/good-corim.yaml b/corim/testcases/src/good-corim.yaml new file mode 100644 index 00000000..4e61fee6 --- /dev/null +++ b/corim/testcases/src/good-corim.yaml @@ -0,0 +1,66 @@ +# minimalist unsigned-corim that embeds comid.PSARefValJSONTemplate +--- +0: test corim id +1: +- encodedCBOR: + tag: 506 + value: + 0: en-GB + 1: + 0: !!binary |- + Q7vjfy5hSzOu01PP8UKLFg== + 2: + - 0: ACME Ltd. + 1: + tag: 32 + value: https://acme.example + 2: + - 0 + - 1 + - 2 + 4: + 0: + - - 0: + 0: + tag: 600 + value: !!binary |- + YWNtZS1pbXBsZW1lbnRhdGlvbi1pZC0wMDAwMDAwMDE= + 1: ACME + 2: RoadRunner + - - 0: + tag: 601 + value: + 1: BL + 4: 2.1.0 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + h0KPxSKAPTEGXnvOPPA/5HUJZjHl4Hu9eg/eYMTPJcc= + - 0: + tag: 601 + value: + 1: PRoT + 4: 1.3.5 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + AmOCmYm2/ZVPcrqvL8ZLwuLwHWktTecphuqAj26ZgT8= + - 0: + tag: 601 + value: + 1: ARoT + 4: 0.1.4 + 5: !!binary |- + rLsRx+TaIXIFUjzkzhokWuGiOa48a/2eeHH35di66Gs= + 1: + 2: + - - 1 + - !!binary |- + o6XnFfDMV0pzw/m+u2vCTzL/1bZ7OHJEwskJ2neaFHg= + diff --git a/corim/testcases/src/meta.yaml b/corim/testcases/src/meta.yaml new file mode 100644 index 00000000..abffebe3 --- /dev/null +++ b/corim/testcases/src/meta.yaml @@ -0,0 +1,13 @@ +--- +1: + 0: + tag: 1 + value: 1640908800 + 1: + tag: 1 + value: 1767139200 +0: + 1: + tag: 32 + value: https://acme.example + 0: ACME Ltd signing key diff --git a/corim/testcases/unsigned-corim-with-extensions.cbor b/corim/testcases/unsigned-corim-with-extensions.cbor new file mode 100644 index 0000000000000000000000000000000000000000..0e056df92a7d13cf5caff8df0858e92e82bb1894 GIT binary patch literal 514 zcmZ3&Tp^!PQc_^0ub*0xm|KvOs+XLft6!2@T%uc0l%JNFld6!MmY>hq7|D3-CgZOq z45_Jky6#Sl3mF2OcR#MzOY}BgcRBd{N2hKvrp6@ zOwLW!gPPCO%)rQ`kZ)*Ytl*ZIovILAQk0rn!m^N|v1uX0Vy3L1{KS-?(!9LXB8Hnx z5fKU?y}Fqom*uABl_ZvA=I7~Vrsx_#fgxiG$jatLjEk5Wn;0V$+MW83DmB;|vc*-O zv-x2Eq?9ww@acoLp=l52!%Df1&=?u zrC7uoWb@>lluFdYMV9Mqvj5JjDE$8P#;zCHiy(SZ0)p~GSW*r3jP*>3&;@fAQ*zVH z&TW6E`WNk5uYb(@(4!BsnYzBuHQQb_^ykfNw1+qgVh2mA0nkk*#F(Dq2y_k1j>Su# yi+(s0?p1vF=f2(9hy0EHU)@%1QRH&yBtIvi2 literal 0 HcmV?d00001 diff --git a/corim/testcases/unsigned-example-corim.cbor b/corim/testcases/unsigned-example-corim.cbor new file mode 100644 index 0000000000000000000000000000000000000000..0c715c8f24d0886906a5f8df3eb40b02d6b4d4dd GIT binary patch literal 517 zcmZ3&*ci!p>?Y%{B`gaW8k-g}EN03I%1=xQD$UDFEn>LI6cM42n4Fuco0(gXlbV~F zSCUwgnV+YdnWAd|1%`|%j?TWW&5MAlBNwxz8tED88L&nutl2Gi{K+lFBGw?AC+DP8 zq8=`?TxXO0cV0!|_op{@y~t)va`ItZq#%_xkvEU2v57H4q1~zfs8WNiAzNJaIhzmm zPf9t{44*!z-dn|gFX6~})#Hm87eN#f6=b>`p3KvJ^CPzNYaDu^$LPzdxj*kA*ocTHdpTfFqS=!Y}mUd4xh z?%SPx$lvJy)os-lMJ|UkT%wSiUzC}vkeR~R7|FQ!CgZOq45_Jky6#Sl3mF2OcR#MzOY}BgcRBd{ zN2hKvrpCog%?yl844ID3zOD*BB`JE0Hxx=TN=gcft@QO1lXFw`QY#X33vyCf7BVz8 zEo4~CaFZz_LIEVBn+X!nP0cGwEXmBz)6GoLHGl#`#uSi&Oj$wsi77#)d3mWt&5M9K zBNwwqD6H8nc>KvN#Uj=qn