Skip to content

Commit c97bc07

Browse files
yogeshbdeshpandethomas-fossati
authored andcommitted
feat(lead verifier): CMW composite evidence parser
Signed-off-by: Yogesh Deshpande <[email protected]>
1 parent 6148276 commit c97bc07

File tree

11 files changed

+353
-7
lines changed

11 files changed

+353
-7
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ require (
3737
github.com/stretchr/testify v1.10.0
3838
github.com/tbaehler/gin-keycloak v1.6.1
3939
github.com/veraison/ccatoken v1.3.2-0.20250512122414-b26aba0635c4
40-
github.com/veraison/cmw v0.2.0
40+
github.com/veraison/cmw v0.3.0
4141
github.com/veraison/corim v1.1.3-0.20251002172919-3c18c66c77b0
4242
github.com/veraison/dice v0.0.1
4343
github.com/veraison/ear v1.1.2

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1223,8 +1223,8 @@ github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4d
12231223
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
12241224
github.com/veraison/ccatoken v1.3.2-0.20250512122414-b26aba0635c4 h1:t2GQueIc1SrErZpprs2ll9ETaXln/nOCPVRq7OejzfQ=
12251225
github.com/veraison/ccatoken v1.3.2-0.20250512122414-b26aba0635c4/go.mod h1:vMqdbW4H/8A3oT+24qssuIK3Aefy06XqzTELGg+gWAg=
1226-
github.com/veraison/cmw v0.2.0 h1:BWEvwZnD4nn5osq6XwQpTRcGxwV+Su4t6ytdAbVXAJY=
1227-
github.com/veraison/cmw v0.2.0/go.mod h1:OiYKk1t6/Fmmg30ZpSMzi4nKr5kt3374sNTkgxC5BDs=
1226+
github.com/veraison/cmw v0.3.0 h1:Uu+Kf76sGseSBXPY6MIGo6CZAqkUUFDt7r6sBN7sB7k=
1227+
github.com/veraison/cmw v0.3.0/go.mod h1:V7OMAyNVrMEWGu1d1LItZRuGJzlO0/KN84ju9XIXmFE=
12281228
github.com/veraison/corim v1.1.3-0.20251002172919-3c18c66c77b0 h1:rl3ANdIHIStRQEYVhU0BaNc87j0W38iWqTwu0HYcuMA=
12291229
github.com/veraison/corim v1.1.3-0.20251002172919-3c18c66c77b0/go.mod h1:NDUWXTTPOnpwTa79HULq+o/6dkgK6XymHdSRGP9YP8M=
12301230
github.com/veraison/dice v0.0.1 h1:dOm7ByDN/r4WlDsGkEUXzdPMXgTvAPTAksQ8+BwBrD4=

vts/compositeevidenceparser/cmw.go

Lines changed: 120 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,126 @@
22
// SPDX-License-Identifier: Apache-2.0
33
package compositeevidenceparser
44

5-
import "github.com/pkg/errors"
5+
import (
6+
"fmt"
67

7-
type cmw struct{}
8+
"github.com/veraison/cmw"
9+
)
810

9-
func (o cmw) Parse(evidence []byte) ([]ComponentEvidence, error) {
10-
return nil, errors.New("not implemented")
11+
const MaxCollectionDepth = 4
12+
13+
type cmwParser struct{}
14+
15+
func (o cmwParser) Parse(evidence []byte) ([]ComponentEvidence, error) {
16+
cmwCollection := &cmw.CMW{}
17+
18+
if err := cmwCollection.Deserialize(evidence); err != nil {
19+
return nil, fmt.Errorf("unable to unmarshal CMW collection: %w", err)
20+
}
21+
22+
if cmwCollection.GetKind() != cmw.KindCollection {
23+
return nil, fmt.Errorf("evidence is not a CMW collection")
24+
}
25+
26+
if err := cmwCollection.Valid(); err != nil {
27+
return nil, fmt.Errorf("unable to validate CMW collection: %w", err)
28+
}
29+
30+
depth := uint(0)
31+
parentLabel := ""
32+
33+
ces, err := parse(cmwCollection, depth, parentLabel)
34+
if err != nil {
35+
return nil, fmt.Errorf("unable to parse CMW collection: %w", err)
36+
}
37+
38+
return ces, nil
39+
}
40+
41+
func parse(c *cmw.CMW, depth uint, parentLabel string) ([]ComponentEvidence, error) {
42+
var ce []ComponentEvidence
43+
44+
meta, err := c.GetCollectionMeta()
45+
if err != nil {
46+
return ce, fmt.Errorf("unable to get collection meta at depth %d: %w", depth, err)
47+
}
48+
49+
for _, k := range meta {
50+
switch k.Kind {
51+
case cmw.KindMonad:
52+
collectionItem, err := c.GetCollectionItem(k.Key)
53+
if err != nil {
54+
return ce, fmt.Errorf("unable to get item for key %v at depth %d: %w", k.Key, depth, err)
55+
}
56+
e, err := componentEvidenceFromMonad(collectionItem, k.Key, depth, parentLabel)
57+
if err != nil {
58+
return ce, fmt.Errorf("unable to create Component Evidence for key %v at depth %d: %w", k.Key, depth, err)
59+
}
60+
ce = append(ce, *e)
61+
case cmw.KindCollection:
62+
depth++
63+
if depth > MaxCollectionDepth {
64+
return ce, fmt.Errorf("collection depth %d: exceeded the maximum limit %d:", depth, MaxCollectionDepth)
65+
}
66+
collectionItem, err := c.GetCollectionItem(k.Key)
67+
if err != nil {
68+
return ce, fmt.Errorf("unable to get item for key %v at depth %d: %w", k.Key, depth, err)
69+
}
70+
parentLabel, err := stringify(k.Key)
71+
if err != nil {
72+
return ce, fmt.Errorf("invalid key %v at depth %d: %w", k.Key, depth, err)
73+
}
74+
ces, err := parse(collectionItem, depth, parentLabel)
75+
if err != nil {
76+
return ce, fmt.Errorf("unable to parse nested collection for key %s at depth %d: %w", parentLabel, depth, err)
77+
}
78+
ce = append(ce, ces...)
79+
case cmw.KindUnknown:
80+
fallthrough
81+
default:
82+
return ce, fmt.Errorf("unknown kind %s for key %v at depth %d", k.Kind.String(), k.Key, depth)
83+
}
84+
}
85+
86+
return ce, nil
87+
}
88+
89+
func componentEvidenceFromMonad(c *cmw.CMW, key any, depth uint, parentLabel string) (*ComponentEvidence, error) {
90+
mt, err := c.GetMonadType()
91+
if err != nil {
92+
return nil, fmt.Errorf("invalid monad type: %w", err)
93+
}
94+
95+
d, err := c.GetMonadValue()
96+
if err != nil {
97+
return nil, fmt.Errorf("invalid monad data: %w", err)
98+
}
99+
100+
k, err := stringify(key)
101+
if err != nil {
102+
return nil, fmt.Errorf("invalid key: %w", err)
103+
}
104+
105+
return &ComponentEvidence{
106+
label: k,
107+
data: d,
108+
mediaType: mt,
109+
parentLabel: parentLabel,
110+
depth: depth,
111+
}, nil
112+
}
113+
114+
func stringify(key any) (string, error) {
115+
switch t := key.(type) {
116+
case string:
117+
return t, nil
118+
case uint64:
119+
return fmt.Sprintf("%d", t), nil
120+
default:
121+
return "", fmt.Errorf("key with unknown type %T", t)
122+
}
123+
}
124+
125+
func (o cmwParser) SupportedMediaTypes() []string {
126+
return []string{"application/cmw+cbor", "application/cmw+json"}
11127
}
Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// Copyright 2026 Contributors to the Veraison project.
2+
// SPDX-License-Identifier: Apache-2.0
3+
package compositeevidenceparser
4+
5+
import (
6+
"encoding/hex"
7+
"os"
8+
"regexp"
9+
"testing"
10+
11+
"github.com/stretchr/testify/assert"
12+
"github.com/stretchr/testify/require"
13+
)
14+
15+
func Test_Parse_CBORCollection_OK(t *testing.T) {
16+
expected := []ComponentEvidence{
17+
{
18+
label: "bretwaldadom",
19+
data: []byte{0xa1, 0x0a},
20+
mediaType: "application/eat-ucs+cbor",
21+
parentLabel: "",
22+
depth: 0,
23+
},
24+
{
25+
label: "polyscopic",
26+
data: []byte(`{"eat_nonce": ...}`),
27+
mediaType: "application/eat-ucs+json",
28+
parentLabel: "murmurless",
29+
depth: 1,
30+
},
31+
{
32+
label: "photoelectrograph",
33+
data: []byte{0x82, 0x78, 0x18},
34+
mediaType: "application/eat-ucs+cbor",
35+
parentLabel: "",
36+
depth: 1,
37+
},
38+
}
39+
40+
b := mustReadFile(t, "testdata/collection-1.cbor")
41+
42+
var cmwParser cmwParser
43+
ev, err := cmwParser.Parse(b)
44+
require.NoError(t, err)
45+
46+
assert.ElementsMatch(t, expected, ev)
47+
}
48+
49+
func Test_ParseJSONCollection_OK(t *testing.T) {
50+
expected := []ComponentEvidence{
51+
{
52+
label: "bretwaldadom",
53+
data: []byte{0xa1, 0x0a},
54+
mediaType: "application/eat-ucs+cbor",
55+
parentLabel: "",
56+
depth: 0,
57+
},
58+
{
59+
label: "polyscopic",
60+
data: []byte(`{"eat_nonce": ...}`),
61+
mediaType: "application/eat-ucs+json",
62+
parentLabel: "murmurless",
63+
depth: 1,
64+
},
65+
{
66+
label: "photoelectrograph",
67+
data: []byte{0x82, 0x78, 0x18},
68+
mediaType: "application/eat-ucs+cbor",
69+
parentLabel: "",
70+
depth: 1,
71+
},
72+
}
73+
74+
b := mustReadFile(t, "testdata/collection-1.json")
75+
76+
var cmwParser cmwParser
77+
ev, err := cmwParser.Parse(b)
78+
require.NoError(t, err)
79+
80+
assert.ElementsMatch(t, expected, ev)
81+
}
82+
83+
func Test_Parse_MixedKey_Collection_OK(t *testing.T) {
84+
expected := []ComponentEvidence{
85+
{
86+
label: "1024",
87+
data: []byte{0xaa},
88+
mediaType: "text/plain; charset=utf-8",
89+
parentLabel: "",
90+
depth: 0,
91+
},
92+
{
93+
label: "string",
94+
data: []byte{0xff},
95+
mediaType: "text/plain; charset=utf-8",
96+
parentLabel: "",
97+
depth: 0,
98+
},
99+
}
100+
101+
tv := mustReadFile(t, "testdata/collection-cbor-mixed-keys.cbor")
102+
103+
var cmwParser cmwParser
104+
ev, err := cmwParser.Parse(tv)
105+
require.NoError(t, err)
106+
107+
assert.ElementsMatch(t, expected, ev)
108+
}
109+
110+
func Test_Parse_Collection_NOK(t *testing.T) {
111+
tests := []struct {
112+
name string
113+
data string
114+
expectedErr string
115+
}{
116+
{
117+
"CMW monad instead of collection",
118+
`83781d6170706c69636174696f6e2f7369676e65642d636f72696d2b63626f724dd901f6d28440a044d901f5a04003`,
119+
`evidence is not a CMW collection`,
120+
},
121+
{
122+
"Invalid CMW CBOR header",
123+
`63781d6170706c69636174696f6e2f7369676e65642d636f72696d2b63626f724dd901f6d28440a044d901f5a04003`,
124+
"unable to unmarshal CMW collection: unknown start symbol for CMW",
125+
},
126+
}
127+
128+
for _, tt := range tests {
129+
var cmwParser cmwParser
130+
cmw, err := hexDecode(tt.data)
131+
require.Nil(t, err)
132+
133+
_, err = cmwParser.Parse(cmw)
134+
assert.ErrorContains(t, err, tt.expectedErr)
135+
}
136+
}
137+
138+
func Test_SupportedMediaTypes(t *testing.T) {
139+
expected := []string{"application/cmw+cbor", "application/cmw+json"}
140+
141+
var cmwParser cmwParser
142+
mtList := cmwParser.SupportedMediaTypes()
143+
144+
assert.ElementsMatch(t, expected, mtList)
145+
}
146+
147+
func hexDecode(s string) ([]byte, error) {
148+
// allow a long hex string to be split over multiple lines (with soft or
149+
// hard tab indentation)
150+
m := regexp.MustCompile("[ \t\n]")
151+
s = m.ReplaceAllString(s, "")
152+
153+
data, err := hex.DecodeString(s)
154+
if err != nil {
155+
return nil, err
156+
}
157+
158+
return data, nil
159+
}
160+
161+
func mustReadFile(t *testing.T, fname string) []byte {
162+
b, err := os.ReadFile(fname)
163+
require.NoError(t, err)
164+
return b
165+
}

vts/compositeevidenceparser/icompositeevidenceparser.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44
package compositeevidenceparser
55

66
type ICompositeEvidenceParser interface {
7+
// SupportedMediaTypes returns a list of supported Collection Media Types
8+
// supported by the Parser
9+
SupportedMediaTypes() []string
710
// Parse returns a list of component evidence payloads together with
811
// relevant identifying metadata.
912
Parse(evidence []byte) ([]ComponentEvidence, error)
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Copyright 2026 Contributors to the Veraison project.
2+
# SPDX-License-Identifier: Apache-2.0
3+
4+
DIAG_TESTDATA := $(wildcard *.diag)
5+
CBOR_TESTDATA := $(DIAG_TESTDATA:.diag=.cbor)
6+
7+
%.cbor: %.diag ; diag2cbor.rb $< > $@
8+
9+
CLEANFILES += $(CBOR_TESTDATA)
10+
11+
all: $(CBOR_TESTDATA)
12+
13+
clean: ; $(RM) -f $(CLEANFILES)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
�h__cmwc_tstag:ietf.org,2024:Xjmurmurless�h__cmwc_tstag:ietf.org,2024:Yjpolyscopic�xapplication/eat-ucs+jsonR{"eat_nonce": ...}lbretwaldadom�xapplication/eat-ucs+cborB�
2+
qphotoelectrograph�xapplication/eat-ucs+cborC�x
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"__cmwc_t":"tag:ietf.org,2024:X",
3+
"murmurless":{
4+
"__cmwc_t":"tag:ietf.org,2024:Y",
5+
"polyscopic":[
6+
"application/eat-ucs+json",
7+
h'7B226561745F6E6F6E6365223A202E2E2E7D',
8+
4
9+
]
10+
},
11+
"bretwaldadom":[
12+
"application/eat-ucs+cbor",
13+
h'A10A'
14+
],
15+
"photoelectrograph":[
16+
"application/eat-ucs+cbor",
17+
h'827818',
18+
4
19+
]
20+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
{
2+
"__cmwc_t": "tag:ietf.org,2024:X",
3+
"bretwaldadom": [
4+
"application/eat-ucs+cbor",
5+
"oQo"
6+
],
7+
"murmurless": {
8+
"__cmwc_t": "tag:ietf.org,2024:Y",
9+
"polyscopic": [
10+
"application/eat-ucs+json",
11+
"eyJlYXRfbm9uY2UiOiAuLi59",
12+
4
13+
]
14+
},
15+
"photoelectrograph": [
16+
"application/eat-ucs+cbor",
17+
"gngY",
18+
4
19+
]
20+
}
Binary file not shown.

0 commit comments

Comments
 (0)