Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Cmw wrap #27

Merged
merged 1 commit into from
May 12, 2023
Merged
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: 8 additions & 2 deletions .github/workflows/linters.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,20 @@ jobs:
lint:
name: Lint
runs-on: ubuntu-latest
env:
GO111MODULE: on
steps:
- name: Setup go
uses: actions/setup-go@v3
with:
go-version: "1.18"
- name: Checkout code
uses: actions/checkout@v2
- name: Install golangci-lint
run: |
go version
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.47.0
curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/master/install.sh | sh -s -- -b $(go env GOPATH)/bin v1.48.0
- name: Run required linters in .golangci.yml plus hard-coded ones here
run: make GOLINT=$(go env GOPATH)/bin/golangci-lint lint
run: make -w GOLINT=$(go env GOPATH)/bin/golangci-lint lint
- name: Run optional linters (not required to pass)
run: make GOLINT=$(go env GOPATH)/bin/golangci-lint lint-extra
15 changes: 13 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
module github.com/veraison/apiclient

go 1.15
go 1.18

require github.com/stretchr/testify v1.7.0
require (
github.com/stretchr/testify v1.8.2
github.com/veraison/cmw v0.1.0
)

require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fxamacker/cbor/v2 v2.4.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/x448/float16 v0.8.4 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
21 changes: 17 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,10 +1,23 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
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=
github.com/fxamacker/cbor/v2 v2.4.0 h1:ri0ArlOR+5XunOP8CRUowT0pSJOwhW098ZCUyskZD88=
github.com/fxamacker/cbor/v2 v2.4.0/go.mod h1:TA1xS00nchWmaBnEIxPSE5oHLuJBAVvqrtAnWBwBCVo=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/veraison/cmw v0.1.0 h1:vD6tBlGPROCW/HlDcG1jh+XUJi5ihrjXatKZBjrv8mU=
github.com/veraison/cmw v0.1.0/go.mod h1:WoBrlgByc6C1FeHhdze1/bQx1kv5d1sWKO5ezEf4Hs4=
github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM=
github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcYsOfg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
2 changes: 1 addition & 1 deletion provisioning/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Package apiclient/provisioning implements the interaction model described in
https://github.com/veraison/veraison/tree/main/docs/api/endorsement-provisioning

Submit
# Submit

The whole API client exchange is handled via a single invocation of the Run()
method.
Expand Down
2 changes: 1 addition & 1 deletion provisioning/provisioning_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ func TestSubmitConfig_Run_no_submit_uri(t *testing.T) {
func TestSubmitConfig_Run_fail_no_server(t *testing.T) {
tv := SubmitConfig{SubmitURI: testSubmitURI}

expectedErr := `submit request failed: Post "http://veraison.example/endorsement-provisioning/v1/submit": dial tcp: lookup veraison.example: no such host`
expectedErr := `submit request failed: Post "http://veraison.example/endorsement-provisioning/v1/submit": dial tcp: lookup veraison.example on 127.0.0.53:53: no such host`

err := tv.Run(testEndorsement, testEndorsementMediaType)
assert.EqualError(t, err, expectedErr)
Expand Down
62 changes: 62 additions & 0 deletions verification/challengeresponse.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,27 @@ import (
"time"

"github.com/veraison/apiclient/common"
"github.com/veraison/cmw"
)

type CmwWrap int

const (
NoWrap CmwWrap = iota
WrapCBOR
WrapJSON
)

type cmwInfo struct {
mt string
s cmw.Serialization
}

var cmwInfoMap = map[CmwWrap]cmwInfo{
WrapCBOR: {mt: "application/vnd.veraison.cmw+cbor", s: cmw.CBORArray},
WrapJSON: {mt: "application/vnd.veraison.cmw+json", s: cmw.JSONArray},
}

// ChallengeResponseConfig holds the configuration for one or more
// challenge-response exchanges
type ChallengeResponseConfig struct {
Expand All @@ -25,6 +44,7 @@ type ChallengeResponseConfig struct {
NewSessionURI string // URI of the "/newSession" endpoint
Client *common.Client // HTTP(s) client connection configuration
DeleteSession bool // explicitly DELETE the session object after we are done
Wrap CmwWrap // when set, wrap the supplied evidence as a Conceptual Message Wrapper(CMW)
}

// Blob wraps a base64 encoded value together with its media type
Expand Down Expand Up @@ -99,6 +119,24 @@ func (cfg *ChallengeResponseConfig) SetDeleteSession(val bool) {
cfg.DeleteSession = val
}

func isValidCmwWrap(val CmwWrap) bool {
switch val {
case NoWrap, WrapCBOR, WrapJSON:
return true
default:
return false
}
}

// SetWrap sets the Wrap parameter using the supplied val
func (cfg *ChallengeResponseConfig) SetWrap(val CmwWrap) error {
if isValidCmwWrap(val) {
cfg.Wrap = val
return nil
}
return fmt.Errorf("invalid CMW Wrap: %d", val)
}

// Run implements the challenge-response protocol FSM invoking the user
// callback. On success, the received Attestation Result is returned.
func (cfg ChallengeResponseConfig) Run() ([]byte, error) {
Expand All @@ -121,9 +159,33 @@ func (cfg ChallengeResponseConfig) Run() ([]byte, error) {
return nil, fmt.Errorf("evidence generation failed: %w", err)
}

if cfg.Wrap != NoWrap {
evidence, mediaType, err = cfg.wrapEvInCMW(evidence, mediaType)
if err != nil {
return nil, err
}
}

return cfg.ChallengeResponse(evidence, mediaType, sessionURI)
}

func (cfg ChallengeResponseConfig) wrapEvInCMW(evidence []byte, mt string) ([]byte, string, error) {
c := &cmw.CMW{}
c.SetMediaType(mt)
c.SetValue(evidence)
c.SetIndicators(cmw.Evidence)
cmi, ok := cmwInfoMap[cfg.Wrap]
if !ok {
return nil, "", fmt.Errorf("unable to get cmw info for Wrap: %d", cfg.Wrap)
}

cm, err := c.Serialize(cmi.s)
if err != nil {
return nil, "", fmt.Errorf("cmw serialization failed: %w", err)
}
return cm, cmi.mt, nil
}

// NewSession runs the first part of the interaction which deals with session
// creation, nonce and token format negotiation. On success, the session object
// is returned together with the URI of the new session endpoint
Expand Down
157 changes: 148 additions & 9 deletions verification/challengeresponse_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@
package verification

import (
"encoding/base64"
"fmt"
"io"
"io/ioutil"
"net/http"
"testing"

Expand All @@ -14,15 +17,18 @@ import (
)

var (
testNonce []byte = []byte{0xde, 0xad, 0xbe, 0xef}
testNonceSz uint = 32
testEvidence []byte = []byte{0x0e, 0x0d, 0x0e}

testBaseURI = "http://veraison.example"
testRelSessionURI = "/challenge-response/v1/session/1"
testSessionURI = testBaseURI + testRelSessionURI
testNewSessionURI = testBaseURI + "/challenge-response/v1/newSession"
testBadURI = `http://veraison.example:80challenge-response/v1/session/1`
testNonce []byte = []byte{0xde, 0xad, 0xbe, 0xef}
testNonceSz uint = 32
testEvidence []byte = []byte{0x0e, 0x0d, 0x0e}
testCMWEvCBOR []byte = []byte{0x83, 0x78, 0x22, 0x61, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x2f, 0x6d, 0x79, 0x2d, 0x65, 0x76, 0x69, 0x64, 0x65, 0x6e, 0x63, 0x65, 0x2d, 0x6d, 0x65, 0x64, 0x69, 0x61, 0x2d, 0x74, 0x79, 0x70, 0x65, 0x43, 0x0e, 0x0d, 0x0e, 0x04}
testCMWCtCBOR string = "application/vnd.veraison.cmw+cbor"
testCMWEvJSON []byte = []byte(`["application/my-evidence-media-type","Dg0O",4]`)
testCMWCtJSON string = "application/vnd.veraison.cmw+json"
testBaseURI = "http://veraison.example"
testRelSessionURI = "/challenge-response/v1/session/1"
testSessionURI = testBaseURI + testRelSessionURI
testNewSessionURI = testBaseURI + "/challenge-response/v1/newSession"
testBadURI = `http://veraison.example:80challenge-response/v1/session/1`
)

type testEvidenceBuilder struct{}
Expand Down Expand Up @@ -156,6 +162,22 @@ func TestChallengeResponseConfig_NewSession_ok(t *testing.T) {
assert.Equal(t, expectedBody, actualBody)
}

func TestChallengeResponseConfig_SetCMWWrap_ok(t *testing.T) {
cfg := ChallengeResponseConfig{}
err := cfg.SetWrap(WrapCBOR)
assert.NoError(t, err)
err = cfg.SetWrap(WrapJSON)
assert.NoError(t, err)
}

func TestChallengeResponseConfig_SetCMWWrap_nok(t *testing.T) {
cfg := ChallengeResponseConfig{}
randomWrap := 57
expectedErr := "invalid CMW Wrap: 57"
err := cfg.SetWrap(CmwWrap(randomWrap))
assert.EqualError(t, err, expectedErr)
}

func TestChallengeResponseConfig_NewSession_server_chosen_nonce_ok(t *testing.T) {
newSessionCreatedBody := `
{
Expand Down Expand Up @@ -759,3 +781,120 @@ func TestChallengeResponseConfig_Run_async_with_explicit_delete_failed(t *testin
assert.EqualError(t, err, "session resource in failed state")
assert.Nil(t, result)
}

func synthesizeSession(mt string, ev []byte) []string {
s := []string{`
{
"nonce": "3q2+7w==",
"expiry": "2030-10-12T07:20:50.52Z",
"accept": [
"application/psa-attestation-token"
],
"status": "waiting"
}`, `
{
"nonce": "3q2+7w==",
"expiry": "2030-10-12T07:20:50.52Z",
"accept": [
"application/psa-attestation-token"
],
"status": "processing",
"evidence": {
"type": "%s",
"value": "%s"
}
}`, `
{
"nonce": "3q2+7w==",
"expiry": "2030-10-12T07:20:50.52Z",
"accept": [
"application/psa-attestation-token"
],
"status": "complete",
"evidence": {
"type": "application/vnd.parallaxsecond.key-attestation.tpm",
"value": "%s"
},
"result": {
"is_valid": true,
"claims": {}
}
}`,
}
evs := base64.StdEncoding.EncodeToString(ev)
s[1] = fmt.Sprintf(s[1], mt, evs)
s[2] = fmt.Sprintf(s[2], evs)
return s
}

func TestChallengeResponseConfig_Run_async_CMWWrap(t *testing.T) {
tvs := []struct {
desc string
wrap CmwWrap
ct string
ev []byte
sessionState []string
}{
{
desc: "test CBOR",
wrap: WrapCBOR,
ct: testCMWCtCBOR,
ev: testCMWEvCBOR,
sessionState: synthesizeSession(testCMWCtCBOR, testCMWEvCBOR),
},
{
desc: "test JSON",
wrap: WrapJSON,
ct: testCMWCtJSON,
ev: testCMWEvJSON,
sessionState: synthesizeSession(testCMWCtJSON, testCMWEvJSON),
},
}
for _, tv := range tvs {
iter := 1
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch iter {
case 1:
assert.Equal(t, http.MethodPost, r.Method)
w.Header().Set("Location", testRelSessionURI)
w.WriteHeader(http.StatusCreated)
_, e := w.Write([]byte(tv.sessionState[0]))
require.Nil(t, e)

iter++
case 2:
assert.Equal(t, http.MethodPost, r.Method)
ev, err := ioutil.ReadAll(r.Body)
require.Nil(t, err)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to move one line up

assert.Equal(t, ev, tv.ev)
assert.Equal(t, r.Header.Get("Content-Type"), tv.ct)
w.WriteHeader(http.StatusAccepted)
_, e := w.Write([]byte(tv.sessionState[1]))
require.Nil(t, e)

iter++
case 3:
assert.Equal(t, http.MethodGet, r.Method)

w.WriteHeader(http.StatusOK)
_, e := w.Write([]byte(tv.sessionState[2]))
require.Nil(t, e)
}
})

client, teardown := common.NewTestingHTTPClient(h)
defer teardown()
cfg := ChallengeResponseConfig{
Nonce: testNonce,
NewSessionURI: testNewSessionURI,
EvidenceBuilder: testEvidenceBuilder{},
Client: client,
Wrap: tv.wrap,
}
expectedResult := `{ "is_valid": true, "claims": {} }`

result, err := cfg.Run()
assert.NoError(t, err)
assert.JSONEq(t, expectedResult, string(result))
}
}
5 changes: 2 additions & 3 deletions verification/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
Package apiclient/verification implements the interaction model described in
https://github.com/veraison/veraison/tree/main/docs/api/challenge-response

Challenge-Response, atomic operation
# Challenge-Response, atomic operation

Using this mode of operation the whole API client exchange is handled
atomically through a single invocation of the Run() method.
Expand All @@ -30,7 +30,6 @@ EvidenceBuilder interface:
return nil, "", errors.New("no match on accepted media types")
}


The user then creates a ChallengeResponseConfig object supplying the callback
and either an explicit nonce:

Expand Down Expand Up @@ -74,7 +73,7 @@ On success, the Attestation Result, is returned as a JSON string:
fmt.Println(string(attestationResult))
}

Challenge-Response, split operation
# Challenge-Response, split operation

Using this mode of operation the client is responsible for dealing with each
API call separately, invoking the right API method at the right time and place.
Expand Down