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
23 changes: 18 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,16 @@ 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.
replacer := strings.NewReplacer(
"-", "+",
"_", "/",
)

return replacer.Replace(value)


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

Expand All @@ -99,15 +110,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 +151,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 +320,3 @@ def substitute_random_corim_id(path):
with tempfile.NamedTemporaryFile(delete=False, mode='w') as tf:
json.dump(data, tf)
return tf.name

35 changes: 34 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,10 +70,42 @@ type EvidenceBlob struct {
Value []byte `json:"value"`
}

type nonce []byte

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

return base64.URLEncoding.DecodeString(v)
}

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

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

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

nonce, err := base64URLToBytes(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"`
Expand Down
20 changes: 7 additions & 13 deletions verification/api/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ package api

import (
"crypto/rand"
"encoding/base64"
"encoding/json"
"errors"
"fmt"
Expand Down Expand Up @@ -113,15 +112,10 @@ func aToU8(v string) (uint8, error) {
return uint8(u8), nil
}

// b64ToBytes attempts at converting the supplied b64-encoded string into a byte
// slice
// b64ToBytes attempts at converting the supplied base64url-encoded string into
// a byte slice.
func b64ToBytes(v string) ([]byte, error) {
b, err := base64.URLEncoding.DecodeString(v)
if err != nil {
return nil, err
}

return b, nil
return base64URLToBytes(v)
}

// parseNonceRequest tries to devise the nonce value to be used for the session
Expand Down Expand Up @@ -164,7 +158,7 @@ func parseNonceRequest(nonceParam, nonceSizeParam string) ([]byte, error) {
return nonce, nil
}

func newSession(nonce []byte, supportedMediaTypes []string, ttl time.Duration) (uuid.UUID, []byte, error) {
func newSession(sessionNonce nonce, supportedMediaTypes []string, ttl time.Duration) (uuid.UUID, []byte, error) {
id, err := mintSessionID()
if err != nil {
return uuid.UUID{}, nil, err
Expand All @@ -173,7 +167,7 @@ func newSession(nonce []byte, supportedMediaTypes []string, ttl time.Duration) (
session := &ChallengeResponseSession{
id: id.String(),
Status: StatusWaiting, // start in waiting status
Nonce: nonce,
Nonce: sessionNonce,
Expiry: time.Now().Add(ttl), // RFC3339 format, with sub-second precision added if present
Accept: supportedMediaTypes,
}
Expand Down Expand Up @@ -460,7 +454,7 @@ func (o *Handler) NewChallengeResponse(c *gin.Context) {
}

// parse query to devise the nonce
nonce, err := parseNonceRequest(c.Query("nonce"), c.Query("nonceSize"))
sessionNonce, err := parseNonceRequest(c.Query("nonce"), c.Query("nonceSize"))
if err != nil {
status := http.StatusBadRequest

Expand All @@ -484,7 +478,7 @@ func (o *Handler) NewChallengeResponse(c *gin.Context) {
return
}

id, session, err := newSession(nonce, supportedMediaTypes, ConfigSessionTTL)
id, session, err := newSession(nonce(sessionNonce), supportedMediaTypes, ConfigSessionTTL)
if err != nil {
ReportProblem(c,
http.StatusInternalServerError,
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.RawURLEncoding.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_-]+$`, responseNonce(t, w.Body.Bytes()))
assert.Len(t, body.Nonce, expectedNonceSize)
assert.Nil(t, body.Evidence)
assert.Nil(t, body.Result)
Expand Down
Loading