Skip to content

Commit 23a5428

Browse files
yogeshbdeshpandethomas-fossati
authored andcommitted
feat: add a Valid method to check a CMW
Also: * add validation logic for monads * fix validation logic for collections This change also deprecates ValidateCollection. Signed-off-by: Yogesh Deshpande <[email protected]> Signed-off-by: Thomas Fossati <[email protected]>
1 parent 0879f49 commit 23a5428

File tree

8 files changed

+223
-50
lines changed

8 files changed

+223
-50
lines changed

cmw.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,13 +153,30 @@ func (o CMW) GetCollectionItem(key any) (*CMW, error) {
153153
return o.collection.getItem(key)
154154
}
155155

156+
// ValidateCollection checks whether a CMW collection is valid or not
157+
//
158+
// Deprecated: use Valid instead
156159
func (o CMW) ValidateCollection() error {
157160
if o.kind != KindCollection {
158161
return fmt.Errorf("want collection, got %q", o.kind)
159162
}
160163
return o.collection.validate()
161164
}
162165

166+
// Valid checks whether a CMW is valid.
167+
// It works for both monad and collection kinds and recursively validates nested structures.
168+
// It returns nil if the CMW is valid, or an error describing the validation failure.
169+
func (o CMW) Valid() error {
170+
switch o.kind {
171+
case KindMonad:
172+
return o.monad.validate()
173+
case KindCollection:
174+
return o.collection.validate()
175+
default:
176+
return fmt.Errorf("unknown kind: %s", o.kind.String())
177+
}
178+
}
179+
163180
type Meta struct {
164181
Key any
165182
Kind Kind

cmw_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,3 +180,19 @@ func Test_GetCollectionGet(t *testing.T) {
180180
assert.EqualError(t, err, `item not found for key "uh?"`)
181181
assert.Nil(t, itemNotFound)
182182
}
183+
184+
func Test_Valid(t *testing.T) {
185+
typ := "text/plain; charset=utf-8"
186+
val := []byte{0xff}
187+
ind := Indicator(Evidence)
188+
189+
cmw, err := NewMonad(typ, val, ind)
190+
require.NoError(t, err)
191+
err = cmw.Valid()
192+
require.NoError(t, err)
193+
194+
cmw = makeCMWCollection()
195+
require.NoError(t, err)
196+
err = cmw.Valid()
197+
require.NoError(t, err)
198+
}

collection.go

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,17 @@ func (o collection) validate() error {
3030
}
3131

3232
for k, v := range o.cmap {
33-
if err := v.validate(); err != nil {
34-
return fmt.Errorf("invalid collection at key %q: %w", k, err)
33+
switch v.kind {
34+
case KindMonad:
35+
if err := v.monad.validate(); err != nil {
36+
return fmt.Errorf("invalid monad at key %q: %w", k, err)
37+
}
38+
case KindCollection:
39+
if err := v.collection.validate(); err != nil {
40+
return fmt.Errorf("invalid collection at key %q: %w", k, err)
41+
}
42+
default:
43+
return fmt.Errorf("unknown CMW kind at key %q: %s", k, v.kind.String())
3544
}
3645
}
3746

collection_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ func Test_Collection_JSON_Deserialize_fail_inner(t *testing.T) {
7979
var actual CMW
8080
err := actual.UnmarshalJSON(tv)
8181
require.NoError(t, err)
82-
err = actual.ValidateCollection()
82+
err = actual.Valid()
8383
assert.EqualError(t, err, `invalid collection at key "a": empty CMW collection`)
8484
}
8585

example_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,10 @@ func Example_roundtrip_JSON_collection() {
147147
if err != nil {
148148
log.Fatalf("unmarshal JSON collection failed: %v", err)
149149
}
150-
150+
err = o.Valid()
151+
if err != nil {
152+
log.Fatalf("validate JSON collection failed: %v", err)
153+
}
151154
b, err := o.MarshalJSON()
152155
if err != nil {
153156
log.Fatalf("marshal collection to JSON failed: %v", err)

indicator.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const (
1818
AttestationResults
1919
TrustAnchors
2020
)
21+
const MaxIndicatorValue = (TrustAnchors << 1) - 1
2122

2223
const IndicatorNone = 0
2324

monad.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package cmw
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67

78
"github.com/fxamacker/cbor/v2"
@@ -62,6 +63,31 @@ func (o *monad) UnmarshalCBOR(b []byte) error {
6263
return nil
6364
}
6465

66+
// validate validates the monad according to draft-ietf-rats-msg-wrap-23 Section 3.1
67+
func (o monad) validate() error {
68+
// Type field MUST be set (either media-type string or CoAP Content-Format ID)
69+
if !o.typ.IsSet() {
70+
return errors.New("type not set")
71+
}
72+
73+
// Value field MUST be set and non-empty
74+
// base64url-encoding for JSON already checked during unmarshaling
75+
if !o.val.IsSet() {
76+
return errors.New("value not set")
77+
}
78+
79+
// - MUST be non-zero if present (this is already checked during unmarshaling)
80+
// - Any combination between 1 and 2^32-1 is allowed
81+
// - However, only bits 0-4 are currently registered (values 1-31)
82+
if !o.ind.Empty() {
83+
if uint(o.ind) > MaxIndicatorValue {
84+
return fmt.Errorf("indicator value %d exceeds maximum %d", uint(o.ind), MaxIndicatorValue)
85+
}
86+
}
87+
88+
return nil
89+
}
90+
6591
func (o monad) encodeCBORTag() ([]byte, error) {
6692
var (
6793
tag cbor.RawTag
@@ -143,6 +169,9 @@ func recordDecode[V json.RawMessage | cbor.RawMessage](
143169
if err := dec(a[2], &o.ind); err != nil {
144170
return fmt.Errorf("unmarshaling indicator: %w", err)
145171
}
172+
if o.ind == 0 {
173+
return errors.New("indicator field, if present, MUST be non-zero")
174+
}
146175
}
147176

148177
return nil

monad_test.go

Lines changed: 144 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -18,55 +18,53 @@ func Test_Deserialize_monad_ok(t *testing.T) {
1818
tv []byte
1919
exp monad
2020
}{
21-
/*
22-
{
23-
"JSON array with media type string",
24-
[]byte(`["application/vnd.intel.sgx", "3q2-7w"]`),
25-
monad{
26-
typ: Type{"application/vnd.intel.sgx"},
27-
val: []byte{0xde, 0xad, 0xbe, 0xef},
28-
ind: IndicatorNone,
29-
format: FormatJSONRecord,
30-
},
21+
{
22+
"JSON array with media type string",
23+
[]byte(`["application/vnd.intel.sgx", "3q2-7w"]`),
24+
monad{
25+
typ: Type{"application/vnd.intel.sgx"},
26+
val: []byte{0xde, 0xad, 0xbe, 0xef},
27+
ind: IndicatorNone,
28+
format: FormatJSONRecord,
3129
},
32-
{
33-
"JSON array with media type string and indicator",
34-
[]byte(`["application/vnd.intel.sgx", "3q2-7w", 31]`),
35-
monad{
36-
Type{"application/vnd.intel.sgx"},
37-
[]byte{0xde, 0xad, 0xbe, 0xef},
38-
testIndicator,
39-
FormatJSONRecord,
40-
},
30+
},
31+
{
32+
"JSON array with media type string and indicator",
33+
[]byte(`["application/vnd.intel.sgx", "3q2-7w", 31]`),
34+
monad{
35+
Type{"application/vnd.intel.sgx"},
36+
[]byte{0xde, 0xad, 0xbe, 0xef},
37+
testIndicator,
38+
FormatJSONRecord,
39+
},
40+
},
41+
{
42+
"CBOR array with CoAP C-F",
43+
// echo "[30001, h'deadbeef']" | diag2cbor.rb | xxd -p -i
44+
[]byte{0x82, 0x19, 0x75, 0x31, 0x44, 0xde, 0xad, 0xbe, 0xef},
45+
monad{
46+
Type{uint16(30001)},
47+
[]byte{0xde, 0xad, 0xbe, 0xef},
48+
IndicatorNone,
49+
FormatCBORRecord,
4150
},
42-
{
43-
"CBOR array with CoAP C-F",
44-
// echo "[30001, h'deadbeef']" | diag2cbor.rb | xxd -p -i
45-
[]byte{0x82, 0x19, 0x75, 0x31, 0x44, 0xde, 0xad, 0xbe, 0xef},
46-
monad{
47-
Type{uint16(30001)},
48-
[]byte{0xde, 0xad, 0xbe, 0xef},
49-
IndicatorNone,
50-
FormatCBORRecord,
51-
},
51+
},
52+
{
53+
"CBOR array with media type string",
54+
// echo "[\"application/vnd.intel.sgx\", h'deadbeef']" | diag2cbor.rb | xxd -p -i
55+
[]byte{
56+
0x82, 0x78, 0x19, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
57+
0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x69,
58+
0x6e, 0x74, 0x65, 0x6c, 0x2e, 0x73, 0x67, 0x78, 0x44, 0xde,
59+
0xad, 0xbe, 0xef,
5260
},
53-
{
54-
"CBOR array with media type string",
55-
// echo "[\"application/vnd.intel.sgx\", h'deadbeef']" | diag2cbor.rb | xxd -p -i
56-
[]byte{
57-
0x82, 0x78, 0x19, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61,
58-
0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x76, 0x6e, 0x64, 0x2e, 0x69,
59-
0x6e, 0x74, 0x65, 0x6c, 0x2e, 0x73, 0x67, 0x78, 0x44, 0xde,
60-
0xad, 0xbe, 0xef,
61-
},
62-
monad{
63-
Type{string("application/vnd.intel.sgx")},
64-
[]byte{0xde, 0xad, 0xbe, 0xef},
65-
IndicatorNone,
66-
FormatCBORRecord,
67-
},
61+
monad{
62+
Type{string("application/vnd.intel.sgx")},
63+
[]byte{0xde, 0xad, 0xbe, 0xef},
64+
IndicatorNone,
65+
FormatCBORRecord,
6866
},
69-
*/
67+
},
7068
{
7169
"CBOR tag",
7270
// echo "1668576818(h'deadbeef')" | diag2cbor.rb | xxd -p -i
@@ -88,7 +86,8 @@ func Test_Deserialize_monad_ok(t *testing.T) {
8886

8987
err := actual.Deserialize(tt.tv)
9088
assert.NoError(t, err)
91-
89+
err = actual.monad.validate()
90+
assert.NoError(t, err)
9291
assert.Equal(t, KindMonad, actual.GetKind())
9392
assert.Equal(t, tt.exp.format, actual.GetFormat())
9493
assert.Equal(t, tt.exp, actual.monad)
@@ -464,3 +463,102 @@ func Test_NewMonad_fail_bad_type(t *testing.T) {
464463
_, err := NewMonad(0xffffffff, []byte{0x00})
465464
assert.EqualError(t, err, `unsupported type int for CMW type`)
466465
}
466+
func Test_Validate_monad_ok(t *testing.T) {
467+
tests := []struct {
468+
name string
469+
typ any
470+
val []byte
471+
ind []Indicator
472+
}{
473+
{
474+
"minimal with string type",
475+
"application/vnd.intel.sgx",
476+
[]byte{0xde, 0xad, 0xbe, 0xef},
477+
[]Indicator{},
478+
},
479+
{
480+
"minimal with CoAP Content-Format",
481+
uint16(30001),
482+
[]byte{0xde, 0xad, 0xbe, 0xef},
483+
[]Indicator{},
484+
},
485+
{
486+
"with single indicator",
487+
"application/eat+cwt",
488+
[]byte{0xde, 0xad, 0xbe, 0xef},
489+
[]Indicator{AttestationResults},
490+
},
491+
{
492+
"with multiple indicators",
493+
"application/corim+signed",
494+
[]byte{0xde, 0xad, 0xbe, 0xef},
495+
[]Indicator{ReferenceValues, Endorsements, TrustAnchors},
496+
},
497+
{
498+
"with maximum valid indicator value (31)",
499+
"application/evidence",
500+
[]byte{0xff},
501+
[]Indicator{Indicator(31)},
502+
},
503+
}
504+
505+
for _, tt := range tests {
506+
t.Run(tt.name, func(t *testing.T) {
507+
cmw, err := NewMonad(tt.typ, tt.val, tt.ind...)
508+
require.NoError(t, err)
509+
510+
err = cmw.monad.validate()
511+
assert.NoError(t, err)
512+
})
513+
}
514+
}
515+
516+
func Test_Validate_monad_fails(t *testing.T) {
517+
tests := []struct {
518+
name string
519+
tv *monad
520+
expectedErr string
521+
}{
522+
{
523+
"type not set",
524+
&monad{
525+
val: []byte{0xde, 0xad, 0xbe, 0xef},
526+
ind: IndicatorNone,
527+
},
528+
"type not set",
529+
},
530+
{
531+
"value not set",
532+
&monad{
533+
typ: Type{"application/evidence"},
534+
ind: IndicatorNone,
535+
},
536+
"value not set",
537+
},
538+
{
539+
"indicator exceeds maximum (32)",
540+
&monad{
541+
typ: Type{"application/evidence"},
542+
val: []byte{0xde, 0xad, 0xbe, 0xef},
543+
ind: Indicator(32),
544+
},
545+
"indicator value 32 exceeds maximum 31",
546+
},
547+
{
548+
"indicator exceeds maximum (255)",
549+
&monad{
550+
typ: Type{"application/evidence"},
551+
val: []byte{0xde, 0xad, 0xbe, 0xef},
552+
ind: Indicator(255),
553+
},
554+
"indicator value 255 exceeds maximum 31",
555+
},
556+
}
557+
558+
for _, tt := range tests {
559+
t.Run(tt.name, func(t *testing.T) {
560+
err := tt.tv.validate()
561+
assert.EqualError(t, err, tt.expectedErr)
562+
})
563+
}
564+
}

0 commit comments

Comments
 (0)