From 5e825126ea92d5996041f1acb87ff31b50ffd199 Mon Sep 17 00:00:00 2001 From: Sergei Trofimov Date: Tue, 26 Sep 2023 12:21:16 +0100 Subject: [PATCH] scheme: freshness check for PSA_IOT and CCA_SSD_PLATFORM Update PSA_IOT and CCA_SSD_PLATFORM schemes to perform a freshness check as part of token integrity validation. Freshness check is integral to attestation validation to prevent replay attacks. Thus far, we have not performed it as part of our verification pipeline, and instead left it the RP. However, PSA and CCA both mandate freshness claims, and so there is no reason why this check cannot be performed as part of the scheme evidence handling, therefore reducing the risk of the RP neglecting to consider freshness and taking an affirming attestation result from Veraison at face value. Signed-off-by: Sergei Trofimov --- .../data/results/cca.freshness-fail.json | 18 +++++ .../data/results/psa.badcrypto.json | 2 +- .../data/results/psa.freshness-fail.json | 17 ++++ integration-tests/tests/common.yaml | 11 ++- .../test_cca_verify_challenge.tavern.yaml | 6 +- .../tests/test_enacttrust_badnode.tavern.yaml | 6 +- .../tests/test_end_to_end.tavern.yaml | 22 +++--- .../test_freshness_check_fail.tavern.yaml | 78 +++++++++++++++++++ .../tests/test_multinonce.tavern.yaml | 6 +- integration-tests/utils/generators.py | 23 +++++- integration-tests/utils/hooks.py | 19 +++++ policy/mocks/mock_ibackend.go | 8 +- scheme/cca-ssd-platform/evidence_handler.go | 14 ++++ .../cca-ssd-platform/evidence_handler_test.go | 15 ++++ scheme/psa-iot/evidence_handler.go | 14 ++++ scheme/psa-iot/evidence_handler_test.go | 12 +++ vts/trustedservices/trustedservices_grpc.go | 2 +- 17 files changed, 245 insertions(+), 28 deletions(-) create mode 100644 integration-tests/data/results/cca.freshness-fail.json create mode 100644 integration-tests/data/results/psa.freshness-fail.json create mode 100644 integration-tests/tests/test_freshness_check_fail.tavern.yaml diff --git a/integration-tests/data/results/cca.freshness-fail.json b/integration-tests/data/results/cca.freshness-fail.json new file mode 100644 index 00000000..a0540e5d --- /dev/null +++ b/integration-tests/data/results/cca.freshness-fail.json @@ -0,0 +1,18 @@ +{ + "ear.appraisal-policy-id": "policy:CCA_SSD_PLATFORM", + "ear.status": "contraindicated", + "ear.trustworthiness-vector": { + "configuration": 99, + "executables": 99, + "file-system": 99, + "hardware": 99, + "instance-identity": 99, + "runtime-opaque": 99, + "sourced-data": 99, + "storage-opaque": 99 + }, + "ear.veraison.policy-claims": { + "problem": "integrity validation failed" + } +} + diff --git a/integration-tests/data/results/psa.badcrypto.json b/integration-tests/data/results/psa.badcrypto.json index 226e6e47..6c2c9189 100644 --- a/integration-tests/data/results/psa.badcrypto.json +++ b/integration-tests/data/results/psa.badcrypto.json @@ -12,6 +12,6 @@ }, "ear.appraisal-policy-id": "policy:PSA_IOT", "ear.veraison.policy-claims": { - "problem": "signature validation failed" + "problem": "integrity validation failed" } } diff --git a/integration-tests/data/results/psa.freshness-fail.json b/integration-tests/data/results/psa.freshness-fail.json new file mode 100644 index 00000000..e7aefb25 --- /dev/null +++ b/integration-tests/data/results/psa.freshness-fail.json @@ -0,0 +1,17 @@ +{ + "ear.appraisal-policy-id": "policy:PSA_IOT", + "ear.status": "contraindicated", + "ear.trustworthiness-vector": { + "configuration": 99, + "executables": 99, + "file-system": 99, + "hardware": 99, + "instance-identity": 99, + "runtime-opaque": 99, + "sourced-data": 99, + "storage-opaque": 99 + }, + "ear.veraison.policy-claims": { + "problem": "integrity validation failed" + } +} diff --git a/integration-tests/tests/common.yaml b/integration-tests/tests/common.yaml index 405f51fa..c729bd3a 100644 --- a/integration-tests/tests/common.yaml +++ b/integration-tests/tests/common.yaml @@ -6,8 +6,15 @@ variables: verification-service: '{tavern.env_vars.VERIFICATION_HOST}.{tavern.env_vars.VERAISON_NETWORK}:{tavern.env_vars.VERIFICATION_PORT}' management-service: '{tavern.env_vars.MANAGEMENT_HOST}.{tavern.env_vars.VERAISON_NETWORK}:{tavern.env_vars.MANAGEMENT_PORT}' keycloak-service: '{tavern.env_vars.KEYCLOAK_HOST}.{tavern.env_vars.VERAISON_NETWORK}:{tavern.env_vars.KEYCLOAK_PORT}' - good-nonce: QUp8F0FBs9DpodKK8xUg8NQimf6sQAfe2J1ormzZLxk= - good-nonce-64: byTWuWNaLIu_WOkIuU4Ewb-zroDN6-gyQkV4SZ_jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA== + nonce32: + value: QUp8F0FBs9DpodKK8xUg8NQimf6sQAfe2J1ormzZLxk= + size: 32 + bad-value: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdea= + nonce64: + value: byTWuWNaLIu_WOkIuU4Ewb-zroDN6-gyQkV4SZ_jF2Hn9eHYvOASGET1Sr36UobaiPU6ZXsVM1yTlrQyklS8XA== + size: 64 + bad-value: deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbe== + bad-nonce: Ppfdfe2JzZLOk= endorsements-content-types: psa.p1: application/corim-unsigned+cbor; profile=http://arm.com/psa/iot/1 diff --git a/integration-tests/tests/test_cca_verify_challenge.tavern.yaml b/integration-tests/tests/test_cca_verify_challenge.tavern.yaml index d2a10242..77bd288b 100644 --- a/integration-tests/tests/test_cca_verify_challenge.tavern.yaml +++ b/integration-tests/tests/test_cca_verify_challenge.tavern.yaml @@ -20,8 +20,10 @@ marks: # Expected structure of the returned EAR (EAT (Entity Attestation # Token) Attestation Result). - expected + # Indicates which nonce configurations ought to be used. + - nonce vals: - - [cca, _, good, full, ccakeys, verify-challenge] + - [cca, _, good, full, ccakeys, verify-challenge, nonce64] includes: - !include common.yaml @@ -65,7 +67,7 @@ stages: - name: verify as relying party - creation of session resource request: method: POST - url: http://{verification-service}/challenge-response/v1/newSession?nonce={good-nonce-64} + url: http://{verification-service}/challenge-response/v1/newSession?nonce={nonce-value} response: status_code: 201 save: diff --git a/integration-tests/tests/test_enacttrust_badnode.tavern.yaml b/integration-tests/tests/test_enacttrust_badnode.tavern.yaml index ad09c251..9d04fd0c 100644 --- a/integration-tests/tests/test_enacttrust_badnode.tavern.yaml +++ b/integration-tests/tests/test_enacttrust_badnode.tavern.yaml @@ -20,8 +20,10 @@ marks: # Expected structure of the returned EAR (EAT (Entity Attestation # Token) Attestation Result). - expected + # Indicates which nonce configurations ought to be used. + - nonce vals: - - [enacttrust, _, badnode, mini, ec.p256.enacttrust, badnode] + - [enacttrust, _, badnode, mini, ec.p256.enacttrust, badnode, nonce32] includes: - !include common.yaml @@ -41,7 +43,7 @@ stages: - name: verify as relying party - creation of session resource request: method: POST - url: http://{verification-service}/challenge-response/v1/newSession?nonce={good-nonce} + url: http://{verification-service}/challenge-response/v1/newSession?nonce={nonce-value} response: status_code: 201 save: diff --git a/integration-tests/tests/test_end_to_end.tavern.yaml b/integration-tests/tests/test_end_to_end.tavern.yaml index b183240b..2a6c0a58 100644 --- a/integration-tests/tests/test_end_to_end.tavern.yaml +++ b/integration-tests/tests/test_end_to_end.tavern.yaml @@ -20,15 +20,17 @@ marks: # Expected structure of the returned EAR (EAT (Entity Attestation # Token) Attestation Result). - expected + # Indicates which nonce configurations ought to be used. + - nonce vals: - - [psa, p1, good, full, ec.p256, good] - - [psa, p1, good, mini, ec.p256, good] - - [psa, p1, missingclaims, mini, ec.p256, noident] - - [psa, p1, good, mini, bad, badcrypto] - - [psa, p1, badinstance, full, ec.p256, badinstance] - - [psa, p1, badswcomp, full, ec.p256, badswcomp] - - [cca, _, good, full, ccakeys, good] - - [enacttrust, _, good, mini, ec.p256.enacttrust, good] + - [psa, p1, good, full, ec.p256, good, nonce32] + - [psa, p1, good, mini, ec.p256, good, nonce32] + - [psa, p1, missingclaims, mini, ec.p256, noident, nonce32] + - [psa, p1, good, mini, bad, badcrypto, nonce32] + - [psa, p1, badinstance, full, ec.p256, badinstance, nonce32] + - [psa, p1, badswcomp, full, ec.p256, badswcomp, nonce32] + - [cca, _, good, full, ccakeys, good, nonce64] + - [enacttrust, _, good, mini, ec.p256.enacttrust, good, nonce32] includes: - !include common.yaml @@ -48,7 +50,7 @@ stages: - name: verify as relying party - creation of session resource request: method: POST - url: http://{verification-service}/challenge-response/v1/newSession?nonce={good-nonce} + url: http://{verification-service}/challenge-response/v1/newSession?nonce={nonce-value} response: status_code: 201 save: @@ -84,7 +86,7 @@ stages: - name: verify as attester - creation of session resource request: method: POST - url: http://{verification-service}/challenge-response/v1/newSession?nonceSize=32 + url: http://{verification-service}/challenge-response/v1/newSession?nonceSize={nonce-size} response: status_code: 201 verify_response_with: diff --git a/integration-tests/tests/test_freshness_check_fail.tavern.yaml b/integration-tests/tests/test_freshness_check_fail.tavern.yaml new file mode 100644 index 00000000..199db4c7 --- /dev/null +++ b/integration-tests/tests/test_freshness_check_fail.tavern.yaml @@ -0,0 +1,78 @@ +test_name: freshness-check-fail + +marks: + - parametrize: + key: + # Attestation scheme -- this is used to indicate how test cases should + # be constructed (e.g. how the evidence token will be compiled. + - scheme + # Some attestation schemes (currently, only PSA) may support multiple + # profiles. If a scheme does not support multiple profiles, specify it + # as '_'. + - profile + # Which evidence description will be used to construct the evidence token. + - evidence + # The name of the endorsements spec within common.yaml + - endorsements + # Signing keys that will be used to construct the evidence. How this is + # used is dependent on the scheme. + - signing + # Expected structure of the returned EAR (EAT (Entity Attestation + # Token) Attestation Result). + - expected + # Indicates which nonce configurations ought to be used. + - nonce + vals: + - [psa, p1, good, full, ec.p256, freshness-fail, nonce32] + - [cca, _, good, full, ccakeys, freshness-fail, nonce64] + +includes: + - !include common.yaml + +stages: + - name: submit post request to the provisioning service successfully + request: + method: POST + url: http://{provisioning-service}/endorsement-provisioning/v1/submit + headers: + content-type: '{endorsements-content-type}' # set via hook + authorization: '{authorization}' # set via hook + file_body: __generated__/endorsements/corim-{scheme}-{endorsements}.cbor + response: + status_code: 200 + + - name: verify as relying party - creation of session resource + request: + method: POST + url: http://{verification-service}/challenge-response/v1/newSession?nonce={nonce-bad-value} + response: + status_code: 201 + save: + headers: + relying-party-session: Location + + - name: verify as relying party - submitting the evidence + request: + method: POST + url: http://{verification-service}/challenge-response/v1/{relying-party-session} + headers: + content-type: '{evidence-content-type}' # set via hook + file_body: __generated__/evidence/{scheme}.{evidence}.cbor + response: + status_code: 200 + verify_response_with: + - function: checkers:save_result + extra_kwargs: + scheme: '{scheme}' + evidence: '{evidence}' + - function: checkers:compare_to_expected_result + extra_kwargs: + expected: data/results/{scheme}.{expected}.json + verifier_key: data/keys/verifier.jwk + + - name: verify as relying party - deleting the session object + request: + method: DELETE + url: http://{verification-service}/challenge-response/v1/{relying-party-session} + response: + status_code: 204 diff --git a/integration-tests/tests/test_multinonce.tavern.yaml b/integration-tests/tests/test_multinonce.tavern.yaml index 34c7037d..a1531cd0 100644 --- a/integration-tests/tests/test_multinonce.tavern.yaml +++ b/integration-tests/tests/test_multinonce.tavern.yaml @@ -20,8 +20,10 @@ marks: # Expected structure of the returned EAR (EAT (Entity Attestation # Token) Attestation Result). - expected + # Indicates which nonce configurations ought to be used. + - nonce vals: - - [psa, p1, multinonce, full, ec.p256, noident] + - [psa, p1, multinonce, full, ec.p256, noident, nonce32] includes: - !include common.yaml @@ -41,7 +43,7 @@ stages: - name: verify - creation of session resource request: method: POST - url: http://{verification-service}/challenge-response/v1/newSession?nonce={good-nonce} + url: http://{verification-service}/challenge-response/v1/newSession?nonce={nonce-value} response: status_code: 201 save: diff --git a/integration-tests/utils/generators.py b/integration-tests/utils/generators.py index 83c1e7ed..319a87b9 100644 --- a/integration-tests/utils/generators.py +++ b/integration-tests/utils/generators.py @@ -43,10 +43,16 @@ def generate_expecte_result_from_response(response, scheme, expected): outfile = f'{GENDIR}/expected/{scheme}.{expected}.server-nonce.json' nonce = response.json()["nonce"] - if scheme in ['psa'] and nonce: + if scheme == 'psa' and nonce: update_json( infile, - {'ear.veraison.annotated-evidence': {f'{scheme}-nonce': nonce}}, + {'ear.veraison.annotated-evidence': {f'psa-nonce': nonce}}, + outfile, + ) + elif scheme == 'cca' and nonce: + update_json( + infile, + {'ear.veraison.annotated-evidence': {'realm': {f'cca-realm-challenge': nonce}}}, outfile, ) else: @@ -56,7 +62,7 @@ def generate_expecte_result_from_response(response, scheme, expected): def generate_evidence_from_test(test): scheme = test.test_vars['scheme'] evidence = test.test_vars['evidence'] - nonce = test.common_vars['good-nonce'] + nonce = test.common_vars[test.test_vars['nonce']]['value'] signing = test.common_vars['keys'][test.test_vars['signing']] outname = f'{scheme}.{evidence}' @@ -75,13 +81,22 @@ def generate_evidence(scheme, evidence, nonce, signing, outname): os.makedirs(f'{GENDIR}/evidence', exist_ok=True) os.makedirs(f'{GENDIR}/claims', exist_ok=True) - if scheme in ['psa', 'cca'] and nonce: + if scheme == 'psa' and nonce: claims_file = f'{GENDIR}/claims/{scheme}.{evidence}.json' update_json( f'data/claims/{scheme}.{evidence}.json', {f'{scheme}-nonce': 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('_', '/') + update_json( + f'data/claims/{scheme}.{evidence}.json', + {'cca-realm-delegated-token': {f'cca-realm-challenge': translated_nonce}}, + claims_file, + ) else: claims_file = f'data/claims/{scheme}.{evidence}.json' diff --git a/integration-tests/utils/hooks.py b/integration-tests/utils/hooks.py index 3be2d92a..55fb8450 100644 --- a/integration-tests/utils/hooks.py +++ b/integration-tests/utils/hooks.py @@ -9,6 +9,7 @@ def setup_end_to_end(test, variables): _set_content_types(test, variables) _set_authorization(test, variables, 'provisioner') + _set_nonce(test, variables) generate_endorsements(test) generate_evidence_from_test(test) @@ -26,6 +27,7 @@ def setup_no_nonce(test, variables): def setup_multi_nonce(test, variables): _set_content_types(test, variables) _set_authorization(test, variables, 'provisioner') + _set_nonce(test, variables) generate_endorsements(test) generate_evidence_from_test_no_nonce(test) @@ -33,6 +35,7 @@ def setup_multi_nonce(test, variables): def setup_enacttrust_badnode(test, variables): _set_authorization(test, variables, 'provisioner') _set_content_types(test, variables) + _set_nonce(test, variables) generate_endorsements(test) generate_evidence_from_test(test) @@ -54,6 +57,15 @@ def setup_cca_verify_challenge(test, variables): _set_content_types(test, variables) _set_authorization(test, variables, 'provisioner') _set_alt_authorization(test, variables, 'manager') + _set_nonce(test, variables) + generate_endorsements(test) + generate_evidence_from_test(test) + + +def setup_freshness_check_fail(test, variables): + _set_content_types(test, variables) + _set_authorization(test, variables, 'provisioner') + _set_nonce(test, variables) generate_endorsements(test) generate_evidence_from_test(test) @@ -75,3 +87,10 @@ def _set_authorization(test, variables, role): def _set_alt_authorization(test, variables, role): token = get_access_token(test, role) variables['alt-authorization'] = f'Bearer {token}' + + +def _set_nonce(test, variables): + nonce_config = test.test_vars['nonce'] + variables['nonce-value'] = test.common_vars[nonce_config]['value'] + variables['nonce-bad-value'] = test.common_vars[nonce_config]['bad-value'] + variables['nonce-size'] = test.common_vars[nonce_config]['size'] diff --git a/policy/mocks/mock_ibackend.go b/policy/mocks/mock_ibackend.go index aff22b6b..69ce2c55 100644 --- a/policy/mocks/mock_ibackend.go +++ b/policy/mocks/mock_ibackend.go @@ -48,18 +48,18 @@ func (mr *MockIBackendMockRecorder) Close() *gomock.Call { } // Evaluate mocks base method. -func (m *MockIBackend) Evaluate(ctx context.Context, appraisalContext map[string]interface{}, scheme, policy string, result, evidence map[string]interface{}, endorsements []string) (map[string]interface{}, error) { +func (m *MockIBackend) Evaluate(ctx context.Context, sessionContext map[string]interface{}, scheme, policy string, result, evidence map[string]interface{}, endorsements []string) (map[string]interface{}, error) { m.ctrl.T.Helper() - ret := m.ctrl.Call(m, "Evaluate", ctx, appraisalContext, scheme, policy, result, evidence, endorsements) + ret := m.ctrl.Call(m, "Evaluate", ctx, sessionContext, scheme, policy, result, evidence, endorsements) ret0, _ := ret[0].(map[string]interface{}) ret1, _ := ret[1].(error) return ret0, ret1 } // Evaluate indicates an expected call of Evaluate. -func (mr *MockIBackendMockRecorder) Evaluate(ctx, appraisalContext, scheme, policy, result, evidence, endorsements interface{}) *gomock.Call { +func (mr *MockIBackendMockRecorder) Evaluate(ctx, sessionContext, scheme, policy, result, evidence, endorsements interface{}) *gomock.Call { mr.mock.ctrl.T.Helper() - return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Evaluate", reflect.TypeOf((*MockIBackend)(nil).Evaluate), ctx, appraisalContext, scheme, policy, result, evidence, endorsements) + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Evaluate", reflect.TypeOf((*MockIBackend)(nil).Evaluate), ctx, sessionContext, scheme, policy, result, evidence, endorsements) } // GetName mocks base method. diff --git a/scheme/cca-ssd-platform/evidence_handler.go b/scheme/cca-ssd-platform/evidence_handler.go index 323f8f25..9793cacb 100644 --- a/scheme/cca-ssd-platform/evidence_handler.go +++ b/scheme/cca-ssd-platform/evidence_handler.go @@ -4,6 +4,8 @@ package cca_ssd_platform import ( + "bytes" + "encoding/hex" "encoding/json" "fmt" @@ -104,6 +106,18 @@ func (s EvidenceHandler) ValidateEvidenceIntegrity( return handler.BadEvidence(err) } + realmChallenge, err := ccaToken.RealmClaims.GetChallenge() + if err != nil { + return handler.BadEvidence(err) + } + if !bytes.Equal(realmChallenge, token.Nonce) { + return handler.BadEvidence( + "freshness: realm challenge (%s) does not match session nonce (%s)", + hex.EncodeToString(realmChallenge), + hex.EncodeToString(token.Nonce), + ) + } + pk, err := arm.GetPublicKeyFromTA(SchemeName, trustAnchor) if err != nil { return fmt.Errorf("could not get public key from trust anchor: %w", err) diff --git a/scheme/cca-ssd-platform/evidence_handler_test.go b/scheme/cca-ssd-platform/evidence_handler_test.go index 37e8aaeb..bffd2013 100644 --- a/scheme/cca-ssd-platform/evidence_handler_test.go +++ b/scheme/cca-ssd-platform/evidence_handler_test.go @@ -16,6 +16,17 @@ import ( "github.com/veraison/services/proto" ) +var testNonce = []byte{ + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, + 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, 0x41, 0x42, +} + func Test_GetTrustAnchorID_ok(t *testing.T) { tokenBytes, err := os.ReadFile("test/cca-token.cbor") require.NoError(t, err) @@ -23,6 +34,7 @@ func Test_GetTrustAnchorID_ok(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } expectedTaID := "CCA_SSD_PLATFORM://1/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=/AQICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIC" @@ -155,6 +167,7 @@ func Test_ExtractVerifiedClaims_ok(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } extracted, err := scheme.ExtractClaims(&token, string(taEndValBytes)) @@ -183,6 +196,7 @@ func Test_ValidateEvidenceIntegrity_ok(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } err = scheme.ValidateEvidenceIntegrity(&token, string(taEndValBytes), nil) @@ -202,6 +216,7 @@ func Test_ValidateEvidenceIntegrity_invalid_key(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } expectedErr := `could not get public key from trust anchor: could not decode subject public key info: unsupported key type: "PRIVATE KEY"` diff --git a/scheme/psa-iot/evidence_handler.go b/scheme/psa-iot/evidence_handler.go index 791ac739..772d4f54 100644 --- a/scheme/psa-iot/evidence_handler.go +++ b/scheme/psa-iot/evidence_handler.go @@ -3,6 +3,8 @@ package psa_iot import ( + "bytes" + "encoding/hex" "encoding/json" "fmt" "log" @@ -86,6 +88,18 @@ func (s EvidenceHandler) ValidateEvidenceIntegrity( return handler.BadEvidence(err) } + psaNonce, err := psaToken.Claims.GetNonce() + if err != nil { + return handler.BadEvidence(err) + } + if !bytes.Equal(psaNonce, token.Nonce) { + return handler.BadEvidence( + "freshness: psa-nonce (%s) does not match session nonce (%s)", + hex.EncodeToString(psaNonce), + hex.EncodeToString(token.Nonce), + ) + } + pk, err := arm.GetPublicKeyFromTA(SchemeName, trustAnchor) if err != nil { return fmt.Errorf("could not get public key from trust anchor: %w", err) diff --git a/scheme/psa-iot/evidence_handler_test.go b/scheme/psa-iot/evidence_handler_test.go index a811dc0a..672ff6de 100644 --- a/scheme/psa-iot/evidence_handler_test.go +++ b/scheme/psa-iot/evidence_handler_test.go @@ -14,6 +14,13 @@ import ( "github.com/veraison/services/proto" ) +var testNonce = []byte{ + 0x07, 0x06, 0x05, 0x04, 0x03, 0x02, 0x01, 0x00, + 0x0f, 0x0e, 0x0d, 0x0c, 0x0b, 0x0a, 0x09, 0x08, + 0x17, 0x16, 0x15, 0x14, 0x13, 0x12, 0x11, 0x10, + 0x1f, 0x1e, 0x1d, 0x1c, 0x1b, 0x1a, 0x19, 0x18, +} + func Test_GetTrustAnchorID_ok(t *testing.T) { tokenBytes, err := os.ReadFile("test/psa-token.cbor") require.NoError(t, err) @@ -21,6 +28,7 @@ func Test_GetTrustAnchorID_ok(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } expectedTaID := "PSA_IOT://1/BwYFBAMCAQAPDg0MCwoJCBcWFRQTEhEQHx4dHBsaGRg=/AQcGBQQDAgEADw4NDAsKCQgXFhUUExIREB8eHRwbGhkY" @@ -44,6 +52,7 @@ func Test_ExtractVerifiedClaimsInteg_ok(t *testing.T) { token := proto.AttestationToken{ TenantId: "0", Data: tokenBytes, + Nonce: testNonce, } _, err = handler.ExtractClaims(&token, string(taEndValBytes)) @@ -64,6 +73,7 @@ func Test_ExtractVerifiedClaims_ok(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } extracted, err := handler.ExtractClaims(&token, string(taEndValBytes)) @@ -88,6 +98,7 @@ func Test_ValidateEvidenceIntegrity_ok(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } err = handler.ValidateEvidenceIntegrity(&token, string(taEndValBytes), nil) @@ -129,6 +140,7 @@ func Test_ValidateEvidenceIntegrity_BadKey(t *testing.T) { token := proto.AttestationToken{ TenantId: "1", Data: tokenBytes, + Nonce: testNonce, } err = h.ValidateEvidenceIntegrity(&token, string(taEndValBytes), nil) diff --git a/vts/trustedservices/trustedservices_grpc.go b/vts/trustedservices/trustedservices_grpc.go index d836e74e..03db670d 100644 --- a/vts/trustedservices/trustedservices_grpc.go +++ b/vts/trustedservices/trustedservices_grpc.go @@ -378,7 +378,7 @@ func (o *GRPC) GetAttestation( if err = handler.ValidateEvidenceIntegrity(token, ta, endorsements); err != nil { if errors.Is(err, handlermod.BadEvidenceError{}) { appraisal.SetAllClaims(ear.CryptoValidationFailedClaim) - appraisal.AddPolicyClaim("problem", "signature validation failed") + appraisal.AddPolicyClaim("problem", "integrity validation failed") } return o.finalize(appraisal, err) }