Skip to content

Commit 931a4be

Browse files
Kallal Mukherjeekallal79
authored andcommitted
Fix #332: Use URL-safe base64 encoding for session nonces
- Add URLSafeNonce type with custom JSON marshaling/unmarshaling - Update ChallengeResponseSession to use URLSafeNonce instead of []byte - Update test cases to use URL-safe base64 encoded nonces - Add comprehensive test to verify URL-safe base64 encoding - Ensure nonces no longer contain '+' and '/' characters This resolves the issue where session nonces were encoded using standard base64 instead of URL-safe base64, making them unsuitable for URL parameters and other web contexts. Signed-off-by: Kallal Mukherjee <[email protected]>
1 parent b8993d2 commit 931a4be

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

verification/api/challengeresponsesession.go

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
package api
77

88
import (
9+
"encoding/base64"
910
"encoding/json"
1011
"fmt"
1112
"time"
@@ -64,6 +65,32 @@ func (o *Status) UnmarshalJSON(b []byte) error {
6465
return o.FromString(s)
6566
}
6667

68+
// URLSafeNonce is a wrapper around []byte that marshals/unmarshals using URL-safe base64
69+
type URLSafeNonce []byte
70+
71+
func (n URLSafeNonce) MarshalJSON() ([]byte, error) {
72+
if n == nil {
73+
return []byte("null"), nil
74+
}
75+
encoded := base64.URLEncoding.EncodeToString(n)
76+
return json.Marshal(encoded)
77+
}
78+
79+
func (n *URLSafeNonce) UnmarshalJSON(data []byte) error {
80+
var s string
81+
if err := json.Unmarshal(data, &s); err != nil {
82+
return err
83+
}
84+
85+
decoded, err := base64.URLEncoding.DecodeString(s)
86+
if err != nil {
87+
return err
88+
}
89+
90+
*n = URLSafeNonce(decoded)
91+
return nil
92+
}
93+
6794
type EvidenceBlob struct {
6895
Type string `json:"type"`
6996
Value []byte `json:"value"`
@@ -72,7 +99,7 @@ type EvidenceBlob struct {
7299
type ChallengeResponseSession struct {
73100
id string
74101
Status Status `json:"status"`
75-
Nonce []byte `json:"nonce"`
102+
Nonce URLSafeNonce `json:"nonce"`
76103
Expiry time.Time `json:"expiry"`
77104
Accept []string `json:"accept"`
78105
Evidence *EvidenceBlob `json:"evidence,omitempty"`

verification/api/handler.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,7 @@ func newSession(nonce []byte, supportedMediaTypes []string, ttl time.Duration) (
168168
session := &ChallengeResponseSession{
169169
id: id.String(),
170170
Status: StatusWaiting, // start in waiting status
171-
Nonce: nonce,
171+
Nonce: URLSafeNonce(nonce),
172172
Expiry: time.Now().Add(ttl), // RFC3339 format, with sub-second precision added if present
173173
Accept: supportedMediaTypes,
174174
}
@@ -394,7 +394,7 @@ func (o *Handler) SubmitEvidence(c *gin.Context) {
394394
// reported if something in the verifier or the connection goes wrong.
395395
// Any problems with the evidence are expected to be reported via the
396396
// attestation result.
397-
attestationResult, err := o.Verifier.ProcessEvidence(tenantID, session.Nonce,
397+
attestationResult, err := o.Verifier.ProcessEvidence(tenantID, []byte(session.Nonce),
398398
evidence, mediaType)
399399
if err != nil {
400400
o.logger.Error(err)

verification/api/handler_test.go

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ var (
4545
testJSONBody = `{ "k": "v" }`
4646
testSession = `{
4747
"status": "waiting",
48-
"nonce": "mVubqtg3Wa5GSrx3L/2B99cQU2bMQFVYUI9aTmDYi64=",
48+
"nonce": "mVubqtg3Wa5GSrx3L_2B99cQU2bMQFVYUI9aTmDYi64=",
4949
"expiry": "2022-07-13T13:50:24.520525+01:00",
5050
"accept": [
5151
"application/eat_cwt;profile=http://arm.com/psa/2.0.0",
@@ -61,7 +61,7 @@ var (
6161
}`
6262
testProcessingSession = `{
6363
"status": "processing",
64-
"nonce": "mVubqtg3Wa5GSrx3L/2B99cQU2bMQFVYUI9aTmDYi64=",
64+
"nonce": "mVubqtg3Wa5GSrx3L_2B99cQU2bMQFVYUI9aTmDYi64=",
6565
"expiry": "2022-07-13T13:50:24.520525+01:00",
6666
"accept": [
6767
"application/eat_cwt;profile=http://arm.com/psa/2.0.0",
@@ -75,7 +75,7 @@ var (
7575
}`
7676
testCompleteSession = `{
7777
"status": "complete",
78-
"nonce": "mVubqtg3Wa5GSrx3L/2B99cQU2bMQFVYUI9aTmDYi64=",
78+
"nonce": "mVubqtg3Wa5GSrx3L_2B99cQU2bMQFVYUI9aTmDYi64=",
7979
"expiry": "2022-07-13T13:50:24.520525+01:00",
8080
"accept": [
8181
"application/eat_cwt;profile=http://arm.com/psa/2.0.0",
@@ -289,12 +289,42 @@ func TestHandler_NewChallengeResponse_NonceParameter(t *testing.T) {
289289
assert.Equal(t, expectedCode, w.Code)
290290
assert.Equal(t, expectedType, w.Result().Header.Get("Content-Type"))
291291
assert.Regexp(t, expectedLocationRE, w.Result().Header.Get("Location"))
292-
assert.Equal(t, expectedNonce, body.Nonce)
292+
assert.Equal(t, expectedNonce, []byte(body.Nonce))
293293
assert.Nil(t, body.Evidence)
294294
assert.Nil(t, body.Result)
295295
assert.Equal(t, expectedSessionStatus, body.Status)
296296
}
297297

298+
func TestURLSafeNonce_EncodingFormat(t *testing.T) {
299+
// Test that nonces with characters that would be URL-unsafe in standard base64
300+
// are properly encoded as URL-safe base64
301+
testNonce := []byte{0x99, 0x5b, 0x9b, 0xaa, 0xd8, 0x37, 0x59, 0xae,
302+
0x46, 0x4a, 0xbc, 0x77, 0x2f, 0xfd, 0x81, 0xf7,
303+
0xd7, 0x10, 0x53, 0x66, 0xcc, 0x40, 0x55, 0x58,
304+
0x50, 0x8f, 0x5a, 0x4e, 0x60, 0xd8, 0x8b, 0xae}
305+
306+
urlSafeNonce := URLSafeNonce(testNonce)
307+
jsonBytes, err := json.Marshal(urlSafeNonce)
308+
require.NoError(t, err)
309+
310+
jsonStr := string(jsonBytes)
311+
t.Logf("Encoded nonce: %s", jsonStr)
312+
313+
// Should not contain URL-unsafe characters '+' or '/'
314+
assert.NotContains(t, jsonStr, "+", "Nonce should not contain '+' character")
315+
assert.NotContains(t, jsonStr, "/", "Nonce should not contain '/' character")
316+
317+
// Should contain URL-safe alternatives '_' and '-' instead
318+
// Note: This specific test nonce should contain '_' character
319+
assert.Contains(t, jsonStr, "_", "URL-safe nonce should contain '_' character")
320+
321+
// Test round-trip: unmarshal and compare
322+
var unmarshaled URLSafeNonce
323+
err = json.Unmarshal(jsonBytes, &unmarshaled)
324+
require.NoError(t, err)
325+
assert.Equal(t, testNonce, []byte(unmarshaled), "Round-trip encoding should preserve nonce data")
326+
}
327+
298328
func TestHandler_NewChallengeResponse_NonceSizeParameter(t *testing.T) {
299329
ctrl := gomock.NewController(t)
300330
defer ctrl.Finish()

0 commit comments

Comments
 (0)