diff --git a/handler/store_rpc.go b/handler/store_rpc.go index f87e6ed3..6456e007 100644 --- a/handler/store_rpc.go +++ b/handler/store_rpc.go @@ -241,13 +241,13 @@ func (s *StoreRPCClient) GetTrustAnchorIDs(token *proto.AttestationToken) ([]str data, err = json.Marshal(token) if err != nil { - return []string{""}, fmt.Errorf("marshaling token: %w", err) + return nil, fmt.Errorf("marshaling token: %w", err) } err = s.client.Call("Plugin.GetTrustAnchorIDs", data, &resp) if err != nil { err = ParseError(err) - return []string{""}, fmt.Errorf("Plugin.GetTrustAnchorIDs RPC call failed: %w", err) // nolint + return nil, fmt.Errorf("Plugin.GetTrustAnchorIDs RPC call failed: %w", err) // nolint } return resp, nil diff --git a/integration-tests/data/claims/cca.good.json b/integration-tests/data/claims/cca.good.json index 3170771b..34f6392f 100644 --- a/integration-tests/data/claims/cca.good.json +++ b/integration-tests/data/claims/cca.good.json @@ -35,7 +35,7 @@ "cca-platform-hash-algo-id": "sha-256" }, "cca-realm-delegated-token": { - "cca-realm-challenge": "byTWuWNaLIu/WOkIuU4Ewb+zroDN6+gyQkV4SZ/jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", + "cca-realm-challenge": "byTWuWNaLIu_WOkIuU4Ewb-zroDN6-gyQkV4SZ_jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", "cca-realm-personalization-value": "QURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBREFEQURBRA==", "cca-realm-initial-measurement": "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==", "cca-realm-extensible-measurements": [ diff --git a/integration-tests/data/results/cca.end-to-end.json b/integration-tests/data/results/cca.end-to-end.json index 0ff5259c..81bc1600 100644 --- a/integration-tests/data/results/cca.end-to-end.json +++ b/integration-tests/data/results/cca.end-to-end.json @@ -61,7 +61,7 @@ "storage-opaque": 0 }, "ear.veraison.annotated-evidence": { - "cca-realm-challenge": "byTWuWNaLIu/WOkIuU4Ewb+zroDN6+gyQkV4SZ/jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", + "cca-realm-challenge": "byTWuWNaLIu_WOkIuU4Ewb-zroDN6-gyQkV4SZ_jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", "cca-realm-extensible-measurements": [ "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==", "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==", diff --git a/integration-tests/data/results/cca.good.json b/integration-tests/data/results/cca.good.json index d2c4b3be..2a282206 100644 --- a/integration-tests/data/results/cca.good.json +++ b/integration-tests/data/results/cca.good.json @@ -63,7 +63,7 @@ "storage-opaque": 0 }, "ear.veraison.annotated-evidence": { - "cca-realm-challenge": "byTWuWNaLIu/WOkIuU4Ewb+zroDN6+gyQkV4SZ/jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", + "cca-realm-challenge": "byTWuWNaLIu_WOkIuU4Ewb-zroDN6-gyQkV4SZ_jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", "cca-realm-extensible-measurements": [ "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==", "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==", diff --git a/integration-tests/data/results/cca.verify-challenge.json b/integration-tests/data/results/cca.verify-challenge.json index c25d176a..873fe3b9 100644 --- a/integration-tests/data/results/cca.verify-challenge.json +++ b/integration-tests/data/results/cca.verify-challenge.json @@ -61,7 +61,7 @@ "storage-opaque": 0 }, "ear.veraison.annotated-evidence": { - "cca-realm-challenge": "byTWuWNaLIu/WOkIuU4Ewb+zroDN6+gyQkV4SZ/jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", + "cca-realm-challenge": "byTWuWNaLIu_WOkIuU4Ewb-zroDN6-gyQkV4SZ_jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA==", "cca-realm-extensible-measurements": [ "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==", "Q0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQ0NDQw==", diff --git a/integration-tests/docker/Dockerfile b/integration-tests/docker/Dockerfile index cde0e6c7..3a6e622d 100644 --- a/integration-tests/docker/Dockerfile +++ b/integration-tests/docker/Dockerfile @@ -23,8 +23,18 @@ RUN apt-get update \ && rm -rf /var/lib/apt/lists/* /var/tmp/* /tmp/* && \ gem install cbor-diag -RUN userdel -f $(cat /etc/passwd | awk -F: "\$3 == ${TESTER_UID}" | cut -d: -f1); \ - groupdel -f $(cat /etc/group | awk -F: "\$3 == ${TESTER_GID}" | cut -d: -f1); \ +# Note: unfortunately this does not get packaged as part of the distro (so +# cannot be installed with apt), and the upstream only provide an amd64 deb, so +# this will not work on arm64 platforms. +RUN wget https://dl.step.sm/gh-release/cli/docs-cli-install/v0.23.1/step-cli_0.23.1_amd64.deb && \ + dpkg -i step-cli_0.23.1_amd64.deb; \ + rm step-cli_0.23.1_amd64.deb + + +RUN user_to_del=$(awk -F: "\$3 == ${TESTER_UID} {print \$1}" /etc/passwd) && \ + [ -n "$user_to_del" ] && userdel -f "$user_to_del" || true && \ + group_to_del=$(awk -F: "\$3 == ${TESTER_GID} {print \$1}" /etc/group) && \ + [ -n "$group_to_del" ] && groupdel -f "$group_to_del" || true && \ groupadd -g ${TESTER_GID} tavern && \ groupadd -g 616 veraison && \ useradd -m -u ${TESTER_UID} -g tavern -G veraison \ diff --git a/integration-tests/utils/checkers.py b/integration-tests/utils/checkers.py index dcf0c61a..81739618 100644 --- a/integration-tests/utils/checkers.py +++ b/integration-tests/utils/checkers.py @@ -38,6 +38,7 @@ def compare_to_expected_result(response, expected, verifier_key): print("Key exists in the dictionary.") except KeyError: print(f"Key {key} does not exist in the dictionary.") + raise assert decoded_claims["ear.status"] == expected_claims["ear.status"] print(f"Evaluating Submod with SubModName {key}") diff --git a/integration-tests/utils/generators.py b/integration-tests/utils/generators.py index 83a2368a..c819796c 100644 --- a/integration-tests/utils/generators.py +++ b/integration-tests/utils/generators.py @@ -128,6 +128,7 @@ def generate_evidence(scheme, evidence, nonce, signing, outname): if scheme == 'psa' and nonce: claims_file = f'{GENDIR}/claims/{scheme}.{evidence}.json' + # Use nonce directly as URL-safe base64 to match verification API update_json( f'data/claims/{scheme}.{evidence}.json', {f'{scheme}-nonce': nonce}, @@ -135,11 +136,10 @@ def generate_evidence(scheme, evidence, nonce, signing, outname): ) elif scheme == 'cca' and nonce: claims_file = f'{GENDIR}/claims/{scheme}.{evidence}.json' - # convert nonce from base64url to base64 - translated_nonce = nonce.replace('-', '+').replace('_', '/') + # Use nonce directly as URL-safe base64 to match verification API update_json( f'data/claims/{scheme}.{evidence}.json', - {'cca-realm-delegated-token': {f'cca-realm-challenge': translated_nonce}}, + {'cca-realm-delegated-token': {f'cca-realm-challenge': nonce}}, claims_file, ) else: diff --git a/log/hclogger.go b/log/hclogger.go index 38474101..bdbc6aa0 100644 --- a/log/hclogger.go +++ b/log/hclogger.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Contributors to the Veraison project. +// Copyright 2022-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package log diff --git a/scheme/arm-cca/store_handler.go b/scheme/arm-cca/store_handler.go index 537a4fbf..2f994f60 100644 --- a/scheme/arm-cca/store_handler.go +++ b/scheme/arm-cca/store_handler.go @@ -48,16 +48,16 @@ func (s StoreHandler) SynthKeysFromTrustAnchor(tenantID string, ta *handler.Endo func (s StoreHandler) GetTrustAnchorIDs(token *proto.AttestationToken) ([]string, error) { evidence, err := ccatoken.DecodeAndValidateEvidenceFromCBOR(token.Data) if err != nil { - return []string{""}, handler.BadEvidence(err) + return nil, handler.BadEvidence(err) } claims := evidence.PlatformClaims if err != nil { - return []string{""}, err + return nil, err } taID, err := arm.GetTrustAnchorID(SchemeName, token.TenantId, claims) if err != nil { - return []string{""}, err + return nil, err } return []string{taID}, nil diff --git a/scheme/parsec-cca/store_handler.go b/scheme/parsec-cca/store_handler.go index 85aca0c8..95cc87c9 100644 --- a/scheme/parsec-cca/store_handler.go +++ b/scheme/parsec-cca/store_handler.go @@ -43,13 +43,13 @@ func (s StoreHandler) GetTrustAnchorIDs(token *proto.AttestationToken) ([]string err := evidence.FromCBOR(token.Data) if err != nil { - return []string{""}, handler.BadEvidence(err) + return nil, handler.BadEvidence(err) } claims := evidence.Pat.PlatformClaims taID, err := arm.GetTrustAnchorID(SchemeName, token.TenantId, claims) if err != nil { - return []string{""}, err + return nil, err } return []string{taID}, nil diff --git a/scheme/parsec-tpm/store_handler.go b/scheme/parsec-tpm/store_handler.go index 27d67cb3..094e4c3a 100644 --- a/scheme/parsec-tpm/store_handler.go +++ b/scheme/parsec-tpm/store_handler.go @@ -42,12 +42,12 @@ func (s StoreHandler) GetTrustAnchorIDs(token *proto.AttestationToken) ([]string var ev tpm.Evidence err := ev.FromCBOR(token.Data) if err != nil { - return []string{""}, handler.BadEvidence(err) + return nil, handler.BadEvidence(err) } kat := ev.Kat if kat == nil { - return []string{""}, errors.New("no key attestation token to fetch Key ID") + return nil, errors.New("no key attestation token to fetch Key ID") } kid := *kat.KID instance_id := base64.StdEncoding.EncodeToString(kid) diff --git a/scheme/psa-iot/store_handler.go b/scheme/psa-iot/store_handler.go index eb7aa000..b04bf150 100644 --- a/scheme/psa-iot/store_handler.go +++ b/scheme/psa-iot/store_handler.go @@ -38,14 +38,14 @@ func (s StoreHandler) SynthKeysFromTrustAnchor(tenantID string, ta *handler.Endo func (s StoreHandler) GetTrustAnchorIDs(token *proto.AttestationToken) ([]string, error) { psaToken, err := psatoken.DecodeAndValidateEvidenceFromCOSE(token.Data) if err != nil { - return []string{""}, handler.BadEvidence(err) + return nil, handler.BadEvidence(err) } claims := psaToken.Claims taID, err := arm.GetTrustAnchorID(SchemeName, token.TenantId, claims) if err != nil { - return []string{""}, err + return nil, err } return []string{taID}, nil diff --git a/scheme/tpm-enacttrust/store_handler.go b/scheme/tpm-enacttrust/store_handler.go index 09aaf5a9..fd15970b 100644 --- a/scheme/tpm-enacttrust/store_handler.go +++ b/scheme/tpm-enacttrust/store_handler.go @@ -43,7 +43,7 @@ func (s StoreHandler) GetTrustAnchorIDs(token *proto.AttestationToken) ([]string strings.Join(EvidenceMediaTypes, ", "), token.MediaType, ) - return []string{""}, err + return nil, err } var decoded Token diff --git a/verification/api/challengeresponsesession.go b/verification/api/challengeresponsesession.go index 33e7035e..76226540 100644 --- a/verification/api/challengeresponsesession.go +++ b/verification/api/challengeresponsesession.go @@ -1,4 +1,4 @@ -// Copyright 2022-2023 Contributors to the Veraison project. +// Copyright 2022-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 // The api package implements the REST API defined in @@ -6,6 +6,7 @@ package api import ( + "encoding/base64" "encoding/json" "fmt" "time" @@ -64,6 +65,32 @@ func (o *Status) UnmarshalJSON(b []byte) error { return o.FromString(s) } +// URLSafeNonce is a wrapper around []byte that marshals/unmarshals using URL-safe base64 +type URLSafeNonce []byte + +func (n URLSafeNonce) MarshalJSON() ([]byte, error) { + if n == nil { + return []byte("null"), nil + } + encoded := base64.URLEncoding.EncodeToString(n) + return json.Marshal(encoded) +} + +func (n *URLSafeNonce) UnmarshalJSON(data []byte) error { + var s string + if err := json.Unmarshal(data, &s); err != nil { + return err + } + + decoded, err := base64.URLEncoding.DecodeString(s) + if err != nil { + return err + } + + *n = URLSafeNonce(decoded) + return nil +} + type EvidenceBlob struct { Type string `json:"type"` Value []byte `json:"value"` @@ -72,7 +99,7 @@ type EvidenceBlob struct { type ChallengeResponseSession struct { id string Status Status `json:"status"` - Nonce []byte `json:"nonce"` + Nonce URLSafeNonce `json:"nonce"` Expiry time.Time `json:"expiry"` Accept []string `json:"accept"` Evidence *EvidenceBlob `json:"evidence,omitempty"` diff --git a/verification/api/handler.go b/verification/api/handler.go index a74d1ee5..58abb8b9 100644 --- a/verification/api/handler.go +++ b/verification/api/handler.go @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Contributors to the Veraison project. +// Copyright 2022-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package api @@ -168,7 +168,7 @@ func newSession(nonce []byte, supportedMediaTypes []string, ttl time.Duration) ( session := &ChallengeResponseSession{ id: id.String(), Status: StatusWaiting, // start in waiting status - Nonce: nonce, + Nonce: URLSafeNonce(nonce), Expiry: time.Now().Add(ttl), // RFC3339 format, with sub-second precision added if present Accept: supportedMediaTypes, } @@ -394,7 +394,7 @@ func (o *Handler) SubmitEvidence(c *gin.Context) { // reported if something in the verifier or the connection goes wrong. // Any problems with the evidence are expected to be reported via the // attestation result. - attestationResult, err := o.Verifier.ProcessEvidence(tenantID, session.Nonce, + attestationResult, err := o.Verifier.ProcessEvidence(tenantID, []byte(session.Nonce), evidence, mediaType) if err != nil { o.logger.Error(err) diff --git a/verification/api/handler_test.go b/verification/api/handler_test.go index 98815aba..75ff23ae 100644 --- a/verification/api/handler_test.go +++ b/verification/api/handler_test.go @@ -1,4 +1,4 @@ -// Copyright 2022-2024 Contributors to the Veraison project. +// Copyright 2022-2025 Contributors to the Veraison project. // SPDX-License-Identifier: Apache-2.0 package api @@ -45,7 +45,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", @@ -61,7 +61,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", @@ -75,7 +75,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", @@ -289,12 +289,42 @@ 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, expectedNonce, []byte(body.Nonce)) assert.Nil(t, body.Evidence) assert.Nil(t, body.Result) assert.Equal(t, expectedSessionStatus, body.Status) } +func TestURLSafeNonce_EncodingFormat(t *testing.T) { + // Test that nonces with characters that would be URL-unsafe in standard base64 + // are properly encoded as URL-safe base64 + testNonce := []byte{0x99, 0x5b, 0x9b, 0xaa, 0xd8, 0x37, 0x59, 0xae, + 0x46, 0x4a, 0xbc, 0x77, 0x2f, 0xfd, 0x81, 0xf7, + 0xd7, 0x10, 0x53, 0x66, 0xcc, 0x40, 0x55, 0x58, + 0x50, 0x8f, 0x5a, 0x4e, 0x60, 0xd8, 0x8b, 0xae} + + urlSafeNonce := URLSafeNonce(testNonce) + jsonBytes, err := json.Marshal(urlSafeNonce) + require.NoError(t, err) + + jsonStr := string(jsonBytes) + t.Logf("Encoded nonce: %s", jsonStr) + + // Should not contain URL-unsafe characters '+' or '/' + assert.NotContains(t, jsonStr, "+", "Nonce should not contain '+' character") + assert.NotContains(t, jsonStr, "/", "Nonce should not contain '/' character") + + // Should contain URL-safe alternatives '_' and '-' instead + // Note: This specific test nonce should contain '_' character + assert.Contains(t, jsonStr, "_", "URL-safe nonce should contain '_' character") + + // Test round-trip: unmarshal and compare + var unmarshaled URLSafeNonce + err = json.Unmarshal(jsonBytes, &unmarshaled) + require.NoError(t, err) + assert.Equal(t, testNonce, []byte(unmarshaled), "Round-trip encoding should preserve nonce data") +} + func TestHandler_NewChallengeResponse_NonceSizeParameter(t *testing.T) { ctrl := gomock.NewController(t) defer ctrl.Finish() diff --git a/vts/trustedservices/trustedservices_grpc.go b/vts/trustedservices/trustedservices_grpc.go index fe876708..f7bee71e 100644 --- a/vts/trustedservices/trustedservices_grpc.go +++ b/vts/trustedservices/trustedservices_grpc.go @@ -442,7 +442,6 @@ func (o *GRPC) GetAttestation( var multEndorsements []string for _, refvalID := range appraisal.EvidenceContext.ReferenceIds { - endorsements, err := o.EnStore.Get(refvalID) if err != nil && !errors.Is(err, kvstore.ErrKeyNotFound) { return o.finalize(appraisal, err) @@ -507,12 +506,12 @@ func (c *GRPC) getTrustAnchors(id []string) ([]string, error) { for _, taID := range id { values, err := c.TaStore.Get(taID) if err != nil { - return []string{""}, err + return nil, err } // For now, Veraison schemes only support one trust anchor per trustAnchorID if len(values) != 1 { - return []string{""}, fmt.Errorf("found %d trust anchors, want 1", len(values)) + return nil, fmt.Errorf("found %d trust anchors, want 1", len(values)) } taValues = append(taValues, values[0]) }