Skip to content
Open
Show file tree
Hide file tree
Changes from 2 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
19 changes: 14 additions & 5 deletions integration-tests/utils/generators.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Copyright 2023-2026 Contributors to the Veraison project.
# SPDX-License-Identifier: Apache-2.0
import ast
import base64
import json
import os
import shutil
Expand Down Expand Up @@ -91,6 +92,12 @@ def generate_artefacts_from_response(response, scheme, evidence, signing, keys,
generate_expected_result_from_response(response, scheme, expected)


def base64url_to_base64(value):
# evcli claim JSON uses standard base64, while session nonces are base64url.
decoded = base64.urlsafe_b64decode(value)
return base64.b64encode(decoded).decode('ascii')
Comment thread
cowbon marked this conversation as resolved.
Outdated


def generate_expected_result_from_response(response, scheme, expected):
os.makedirs(f'{GENDIR}/expected', exist_ok=True)

Expand All @@ -99,15 +106,17 @@ def generate_expected_result_from_response(response, scheme, expected):
nonce = response.json()["nonce"]

if scheme == 'psa' and nonce:
translated_nonce = base64url_to_base64(nonce)
update_json(
infile,
{"PSA_IOT": {'ear.veraison.annotated-evidence': {f'psa-nonce': nonce}}},
{"PSA_IOT": {'ear.veraison.annotated-evidence': {f'psa-nonce': translated_nonce}}},
outfile,
)
elif scheme == 'cca' and nonce:
translated_nonce = base64url_to_base64(nonce)
update_json(
infile,
{"CCA_REALM": {'ear.veraison.annotated-evidence': {f'cca-realm-challenge': nonce}}},
{"CCA_REALM": {'ear.veraison.annotated-evidence': {f'cca-realm-challenge': translated_nonce}}},
outfile,
)
else:
Expand Down Expand Up @@ -138,15 +147,16 @@ def generate_evidence(scheme, evidence, nonce, signing, outname):

if scheme == 'psa' and nonce:
claims_file = f'{GENDIR}/claims/{scheme}.{evidence}.json'
translated_nonce = base64url_to_base64(nonce)
Comment thread
cowbon marked this conversation as resolved.
update_json(
f'data/claims/{scheme}.{evidence}.json',
{f'{scheme}-nonce': nonce},
{f'{scheme}-nonce': translated_nonce},
claims_file,
)
elif scheme == 'cca' and nonce:
claims_file = f'{GENDIR}/claims/{scheme}.{evidence}.json'
# convert nonce from base64url to base64
translated_nonce = nonce.replace('-', '+').replace('_', '/')
translated_nonce = base64url_to_base64(nonce)
update_json(
f'data/claims/{scheme}.{evidence}.json',
{'cca-realm-delegated-token': {f'cca-realm-challenge': translated_nonce}},
Expand Down Expand Up @@ -306,4 +316,3 @@ def substitute_random_corim_id(path):
with tempfile.NamedTemporaryFile(delete=False, mode='w') as tf:
json.dump(data, tf)
return tf.name

41 changes: 40 additions & 1 deletion verification/api/challengeresponsesession.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package api

import (
"encoding/base64"
"encoding/json"
"fmt"
"time"
Expand Down Expand Up @@ -69,16 +70,54 @@ type EvidenceBlob struct {
Value []byte `json:"value"`
}

type nonce []byte

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

func (o *nonce) UnmarshalJSON(b []byte) error {
var v string

if err := json.Unmarshal(b, &v); err != nil {
return err
}

nonce, err := decodeSessionNonce(v)
if err != nil {
return fmt.Errorf("nonce must be valid base64url: %w", err)
}

*o = nonce

return nil
}

type ChallengeResponseSession struct {
id string
Status Status `json:"status"`
Nonce []byte `json:"nonce"`
Nonce nonce `json:"nonce"`
Expiry time.Time `json:"expiry"`
Accept []string `json:"accept"`
Evidence *EvidenceBlob `json:"evidence,omitempty"`
Result *string `json:"result,omitempty"`
}

func decodeSessionNonce(v string) ([]byte, error) {
nonce, err := base64.URLEncoding.DecodeString(v)
if err == nil {
return nonce, nil
}

// Keep reading sessions created before the nonce wire format switched
// from standard base64 to base64url.
if nonce, stdErr := base64.StdEncoding.DecodeString(v); stdErr == nil {
return nonce, nil
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Suggested change
// Keep reading sessions created before the nonce wire format switched
// from standard base64 to base64url.
if nonce, stdErr := base64.StdEncoding.DecodeString(v); stdErr == nil {
return nonce, nil
}

return nil, err
}

func (o *ChallengeResponseSession) SetEvidence(mt string, evidence []byte) {
o.Evidence = &EvidenceBlob{Type: mt, Value: evidence}
}
Expand Down
28 changes: 21 additions & 7 deletions verification/api/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package api

import (
"bytes"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -45,7 +46,7 @@ var (
testJSONBody = `{ "k": "v" }`
testSession = `{
"status": "waiting",
"nonce": "mVubqtg3Wa5GSrx3L/2B99cQU2bMQFVYUI9aTmDYi64=",
"nonce": "mVubqtg3Wa5GSrx3L_2B99cQU2bMQFVYUI9aTmDYi64=",
"expiry": "2022-07-13T13:50:24.520525+01:00",
"accept": [
"application/eat_cwt;profile=\"http://arm.com/psa/2.0.0\"",
Expand All @@ -61,7 +62,7 @@ var (
}`
testProcessingSession = `{
"status": "processing",
"nonce": "mVubqtg3Wa5GSrx3L/2B99cQU2bMQFVYUI9aTmDYi64=",
"nonce": "mVubqtg3Wa5GSrx3L_2B99cQU2bMQFVYUI9aTmDYi64=",
"expiry": "2022-07-13T13:50:24.520525+01:00",
"accept": [
"application/eat_cwt;profile=\"http://arm.com/psa/2.0.0\"",
Expand All @@ -75,7 +76,7 @@ var (
}`
testCompleteSession = `{
"status": "complete",
"nonce": "mVubqtg3Wa5GSrx3L/2B99cQU2bMQFVYUI9aTmDYi64=",
"nonce": "mVubqtg3Wa5GSrx3L_2B99cQU2bMQFVYUI9aTmDYi64=",
"expiry": "2022-07-13T13:50:24.520525+01:00",
"accept": [
"application/eat_cwt;profile=\"http://arm.com/psa/2.0.0\"",
Expand Down Expand Up @@ -117,6 +118,17 @@ var (
}
)

func responseNonce(t *testing.T, response []byte) string {
t.Helper()

var body struct {
Nonce string `json:"nonce"`
}
require.NoError(t, json.Unmarshal(response, &body))

return body.Nonce
}

func TestHandler_NewChallengeResponse_UnsupportedAccept(t *testing.T) {
h := &Handler{}

Expand Down Expand Up @@ -269,11 +281,11 @@ func TestHandler_NewChallengeResponse_NonceParameter(t *testing.T) {
expectedType := ChallengeResponseSessionMediaType
expectedLocationRE := sessionURIRegexp
expectedSessionStatus := StatusWaiting
expectedNonce := []byte("nonce-value")
expectedNonce := testNonce
expectedNonceURLSafe := base64.URLEncoding.EncodeToString(expectedNonce)

qParams := url.Values{}
// b64("nonce-value") => "bm9uY2UtdmFsdWU="
qParams.Add("nonce", "bm9uY2UtdmFsdWU=")
qParams.Add("nonce", expectedNonceURLSafe)

w := httptest.NewRecorder()

Expand All @@ -289,7 +301,8 @@ func TestHandler_NewChallengeResponse_NonceParameter(t *testing.T) {
assert.Equal(t, expectedCode, w.Code)
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
assert.Regexp(t, expectedLocationRE, w.Result().Header.Get("Location"))
assert.Equal(t, expectedNonce, body.Nonce)
assert.Equal(t, expectedNonceURLSafe, responseNonce(t, w.Body.Bytes()))
assert.Equal(t, expectedNonce, []byte(body.Nonce))
assert.Nil(t, body.Evidence)
assert.Nil(t, body.Result)
assert.Equal(t, expectedSessionStatus, body.Status)
Expand Down Expand Up @@ -334,6 +347,7 @@ func TestHandler_NewChallengeResponse_NonceSizeParameter(t *testing.T) {
assert.Equal(t, expectedCode, w.Code)
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
assert.Regexp(t, expectedLocationRE, w.Result().Header.Get("Location"))
assert.Regexp(t, `^[A-Za-z0-9_-]+={0,2}$`, responseNonce(t, w.Body.Bytes()))
assert.Len(t, body.Nonce, expectedNonceSize)
assert.Nil(t, body.Evidence)
assert.Nil(t, body.Result)
Expand Down
Loading