Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ claim | CBOR key id | Supported?
--|--|--
eat_nonce | 10 | ✅
ueid | 256 | ✅
sueids | 257 | :x:
oemid | 258 | :warning: currently `oemid-pem (int)` is not supported
sueids | 257 |
oemid | 258 |
hwmodel | 259 | ✅
hwversion | 260 | ✅
uptime | 261 | ✅
Expand All @@ -24,13 +24,13 @@ eat_profile | 265 | ✅
submods | 266 | ✅
bootcount | 267 | ✅
bootseed | 268 | ✅
dloas | 269 | :x:
dloas | 269 |
swname | 270 | ✅
swversion | 271 | ✅
manifests | 272 | ⚠️ (see [Supported Type for Manifests and Measurements](#supported-type-for-manifests-and-measurements))
measurements | 273 | ⚠️ (see [Supported Type for Manifests and Measurements](#supported-type-for-manifests-and-measurements))
measres | 274 | :x:
intuse | 275 | :x:
measres | 274 |
intuse | 275 |

## Supported CWT Features

Expand Down
106 changes: 106 additions & 0 deletions dloas.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
// Copyright 2020 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package eat

import (
"encoding/json"
"fmt"
"net/url"

cbor "github.com/fxamacker/cbor/v2"
)

type Dloa struct {
Registrar url.URL
PlatformLabel string
ApplicationLabel *string
}

//nolint:gocritic
func (d Dloa) MarshalJSON() ([]byte, error) {
tmp := []interface{}{d.Registrar.String(), d.PlatformLabel}
if d.ApplicationLabel != nil {
tmp = append(tmp, *d.ApplicationLabel)
}
return json.Marshal(tmp)
}

//nolint:dupl
func (d *Dloa) UnmarshalJSON(data []byte) error {
var tmp []json.RawMessage
if err := json.Unmarshal(data, &tmp); err != nil {
return err
}
if len(tmp) < 2 || len(tmp) > 3 {
return fmt.Errorf("invalid array length: %d", len(tmp))
}

var regStr string
if err := json.Unmarshal(tmp[0], &regStr); err != nil {
return err
}
regURL, err := url.Parse(regStr)
if err != nil {
return err
}
d.Registrar = *regURL

if err := json.Unmarshal(tmp[1], &d.PlatformLabel); err != nil {
return err
}

if len(tmp) == 3 {
var appLabel string
if err := json.Unmarshal(tmp[2], &appLabel); err != nil {
return err
}
d.ApplicationLabel = &appLabel
}

return nil
}

//nolint:gocritic
func (d Dloa) MarshalCBOR() ([]byte, error) {
tmp := []interface{}{d.Registrar.String(), d.PlatformLabel}
if d.ApplicationLabel != nil {
tmp = append(tmp, *d.ApplicationLabel)
}
return em.Marshal(tmp)
}

//nolint:dupl
func (d *Dloa) UnmarshalCBOR(data []byte) error {
var tmp []cbor.RawMessage
if err := dm.Unmarshal(data, &tmp); err != nil {
return err
}
if len(tmp) < 2 || len(tmp) > 3 {
return fmt.Errorf("invalid array length: %d", len(tmp))
}

var regStr string
if err := dm.Unmarshal(tmp[0], &regStr); err != nil {
return err
}
regURL, err := url.Parse(regStr)
if err != nil {
return err
}
d.Registrar = *regURL

if err := dm.Unmarshal(tmp[1], &d.PlatformLabel); err != nil {
return err
}

if len(tmp) == 3 {
var appLabel string
if err := dm.Unmarshal(tmp[2], &appLabel); err != nil {
return err
}
d.ApplicationLabel = &appLabel
}

return nil
}
110 changes: 110 additions & 0 deletions dloas_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
// Copyright 2020 Contributors to the Veraison project.
// SPDX-License-Identifier: Apache-2.0

package eat

import (
"encoding/json"
"net/url"
"testing"

"github.com/stretchr/testify/assert"
)

func TestDloas_MarshalJSON_OK(t *testing.T) {
expectedUrl, _ := url.Parse("http://example.com/")

len2data := `["http://example.com/","foo"]`
var dloa Dloa

assert.Nil(t, json.Unmarshal([]byte(len2data), &dloa))
assert.Equal(t, *expectedUrl, dloa.Registrar)
assert.Equal(t, "foo", dloa.PlatformLabel)
assert.Nil(t, dloa.ApplicationLabel)

encoded, err := json.Marshal(dloa)
assert.Nil(t, err)
assert.JSONEq(t, len2data, string(encoded))

len3data := `["http://example.com/", "foo", "bar"]`
assert.Nil(t, json.Unmarshal([]byte(len3data), &dloa))
assert.Equal(t, *expectedUrl, dloa.Registrar)
assert.Equal(t, "foo", dloa.PlatformLabel)
assert.Equal(t, "bar", *dloa.ApplicationLabel)

encoded, err = json.Marshal(dloa)
assert.Nil(t, err)
assert.JSONEq(t, len3data, string(encoded))
}

func TestDloas_UnmarshalJSON_NG(t *testing.T) {
var dloa Dloa
assert.NotNil(t, json.Unmarshal([]byte(`0`), &dloa))
assert.NotNil(t, json.Unmarshal([]byte(`["not url"]`), &dloa))
assert.NotNil(t, json.Unmarshal([]byte(`["http://example.com/"]`), &dloa))
assert.NotNil(t, json.Unmarshal([]byte(`["http://example.com/",0]`), &dloa))
assert.NotNil(t, json.Unmarshal([]byte(`["http://example.com/","foo","bar","baz"]`), &dloa))
}

func TestDloas_MarshalCBOR_OK(t *testing.T) {
expectedUrl, _ := url.Parse("http://example.com/")

len2data := []byte{
0x82, // array(2)
0x73, // text(19) "http://example.com/"
0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x63, // text(3)
0x66, 0x6f, 0x6f, // "foo"
}
var dloa Dloa

assert.Nil(t, dm.Unmarshal(len2data, &dloa))
assert.Equal(t, *expectedUrl, dloa.Registrar)
assert.Equal(t, "foo", dloa.PlatformLabel)
assert.Nil(t, dloa.ApplicationLabel)

encoded, err := em.Marshal(dloa)
assert.Nil(t, err)
assert.Equal(t, len2data, encoded)

len3data := []byte{
0x83, // array(3)
0x73, // text(19) "http://example.com/"
0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
0x63, // text(3)
0x66, 0x6f, 0x6f, // "foo"
0x63, // text(3)
0x62, 0x61, 0x72, // "bar"
}
assert.Nil(t, dm.Unmarshal(len3data, &dloa))
assert.Equal(t, *expectedUrl, dloa.Registrar)
assert.Equal(t, "foo", dloa.PlatformLabel)
assert.Equal(t, "bar", *dloa.ApplicationLabel)

encoded, err = em.Marshal(dloa)
assert.Nil(t, err)
assert.Equal(t, len3data, encoded)
}

func TestDloas_UnmarshalCBOR_NG(t *testing.T) {
var dloa Dloa
assert.NotNil(t, dm.Unmarshal([]byte{0x00}, &dloa))
// echo '["not url"]' | diag2cbor.rb | xxd -p
assert.NotNil(t, dm.Unmarshal([]byte{0x81, 0x67, 0x6e, 0x6f, 0x74, 0x20, 0x75, 0x72, 0x6c}, &dloa))
// echo '["http://example.com/"]' | diag2cbor.rb | xxd -p
assert.NotNil(t, dm.Unmarshal([]byte{
0x81, 0x73, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f,
}, &dloa))
// echo '["http://example.com/",0]' | diag2cbor.rb | xxd -p
assert.NotNil(t, dm.Unmarshal([]byte{
0x82, 0x73, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x00,
}, &dloa))
// echo '["http://example.com/","foo","bar","baz"]' | diag2cbor.rb | xxd -p
assert.NotNil(t, dm.Unmarshal([]byte{
0x84, 0x73, 0x68, 0x74, 0x74, 0x70, 0x3a, 0x2f, 0x2f, 0x65, 0x78, 0x61,
0x6d, 0x70, 0x6c, 0x65, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x63, 0x66, 0x6f,
0x6f, 0x63, 0x62, 0x61, 0x72, 0x63, 0x62, 0x61, 0x7a,
}, &dloa))
}
79 changes: 57 additions & 22 deletions eat.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,34 @@
package eat

import (
"encoding/base64"
"encoding/json"
"fmt"
)

// Eat is the internal representation of a EAT token
type Eat struct {
Nonce *Nonce `cbor:"10,keyasint,omitempty" json:"eat_nonce,omitempty"`
UEID *UEID `cbor:"256,keyasint,omitempty" json:"ueid,omitempty"`
// TODO: support SUEIDs
// TODO: support oemid-pem = int type
OemID *[]byte `cbor:"258,keyasint,omitempty" json:"oemid,omitempty"`
HardwareModel *[]byte `cbor:"259,keyasint,omitempty" json:"hwmodel,omitempty"`
HardwareVersion *Version `cbor:"260,keyasint,omitempty" json:"hwversion,omitempty"`
Uptime *uint `cbor:"261,keyasint,omitempty" json:"uptime,omitempty"`
OemBoot *bool `cbor:"262,keyasint,omitempty" json:"oemboot,omitempty"`
DebugStatus *Debug `cbor:"263,keyasint,omitempty" json:"dbgstat,omitempty"`
Location *Location `cbor:"264,keyasint,omitempty" json:"location,omitempty"`
Profile *Profile `cbor:"265,keyasint,omitempty" json:"eat-profile,omitempty"`
Submods *Submods `cbor:"266,keyasint,omitempty" json:"submods,omitempty"`
BootCount *uint `cbor:"267,keyasint,omitempty" json:"bootcount,omitempty"`
BootSeed *[]byte `cbor:"268,keyasint,omitempty" json:"bootseed,omitempty"`
// TODO: DLOAs
SoftwareName *StringOrURI `cbor:"270,keyasint,omitempty" json:"swname,omitempty"`
SoftwareVersion *Version `cbor:"271,keyasint,omitempty" json:"swversion,omitempty"`
Manifests *[]Manifest `cbor:"272,keyasint,omitempty" json:"manifests,omitempty"`
Measurements *[]Measurement `cbor:"273,keyasint,omitempty" json:"measurements,omitempty"`
// TODO: MeasrementResults
// TODO: IntendedUse
Nonce *Nonce `cbor:"10,keyasint,omitempty" json:"eat_nonce,omitempty"`
UEID *UEID `cbor:"256,keyasint,omitempty" json:"ueid,omitempty"`
SUEIDs *map[string]UEID `cbor:"257,keyasint,omitempty" json:"sueids,omitempty"`
OemID *OEMID `cbor:"258,keyasint,omitempty" json:"oemid,omitempty"`
HardwareModel *B64Url `cbor:"259,keyasint,omitempty" json:"hwmodel,omitempty"`
HardwareVersion *Version `cbor:"260,keyasint,omitempty" json:"hwversion,omitempty"`
Uptime *uint `cbor:"261,keyasint,omitempty" json:"uptime,omitempty"`
OemBoot *bool `cbor:"262,keyasint,omitempty" json:"oemboot,omitempty"`
DebugStatus *Debug `cbor:"263,keyasint,omitempty" json:"dbgstat,omitempty"`
Location *Location `cbor:"264,keyasint,omitempty" json:"location,omitempty"`
Profile *Profile `cbor:"265,keyasint,omitempty" json:"eat-profile,omitempty"`
Submods *Submods `cbor:"266,keyasint,omitempty" json:"submods,omitempty"`
BootCount *uint `cbor:"267,keyasint,omitempty" json:"bootcount,omitempty"`
BootSeed *B64Url `cbor:"268,keyasint,omitempty" json:"bootseed,omitempty"`
DLOAs *Dloa `cbor:"269,keyasint,omitempty" json:"dloas,omitempty"`
SoftwareName *StringOrURI `cbor:"270,keyasint,omitempty" json:"swname,omitempty"`
SoftwareVersion *Version `cbor:"271,keyasint,omitempty" json:"swversion,omitempty"`
Manifests *[]Manifest `cbor:"272,keyasint,omitempty" json:"manifests,omitempty"`
Measurements *[]Measurement `cbor:"273,keyasint,omitempty" json:"measurements,omitempty"`
MeasrementResults *[]MeasurementResultsGroup `cbor:"274,keyasint,omitempty" json:"measres,omitempty"`
IntendedUse *IntendedUse `cbor:"275,keyasint,omitempty" json:"intuse,omitempty"`
CWTClaims
}

Expand All @@ -57,3 +58,37 @@ func (e *Eat) FromJSON(data []byte) error {
func (e Eat) ToJSON() ([]byte, error) {
return json.Marshal(e)
}

// B64Url is base64url (§5 of RFC4648) without padding.
// bstr MUST be base64url encoded as per EAT §7.2.2 "JSON Interoperability".
type B64Url []byte

func (o B64Url) MarshalJSON() ([]byte, error) {
return json.Marshal(
base64.RawURLEncoding.EncodeToString(o),
)
}

func (b *B64Url) UnmarshalJSON(data []byte) error {
// get string body
var encoded string
if err := json.Unmarshal(data, &encoded); err != nil {
return err
}

// while base64.RawURLEncoding.DecodeString("") returns
// no err, we need to return err because the CDDL definition is here,
// base64-url-text = tstr .regexp "[A-Za-z0-9_-]+"
if encoded == "" {
return fmt.Errorf("base64url must be a non-empty string")
}

// decode base64url-encoded string
decoded, err := base64.RawURLEncoding.DecodeString(encoded)
if err != nil {
return fmt.Errorf("base64url decode error: %w", err)
}

*b = decoded
return nil
}
Loading