| # ----------------------------------------------------------------------------- |
| # Copyright (c) 2019-2022, Arm Limited. All rights reserved. |
| # |
| # SPDX-License-Identifier: BSD-3-Clause |
| # |
| # ----------------------------------------------------------------------------- |
| |
| import os |
| import tempfile |
| import unittest |
| |
| from iatverifier.psa_iot_profile1_token_verifier import PSAIoTProfile1TokenVerifier |
| from iatverifier.util import convert_map_to_token_files, extract_iat_from_cose |
| from iatverifier.attest_token_verifier import VerifierConfiguration |
| |
| |
| THIS_DIR = os.path.dirname(__file__) |
| |
| DATA_DIR = os.path.join(THIS_DIR, 'data') |
| KEYFILE = os.path.join(DATA_DIR, 'key.pem') |
| KEYFILE_ALT = os.path.join(DATA_DIR, 'key-alt.pem') |
| |
| |
| def create_token(source_name, keyfile, verifier): |
| source_path = os.path.join(DATA_DIR, source_name) |
| fd, dest_path = tempfile.mkstemp() |
| os.close(fd) |
| convert_map_to_token_files(source_path, keyfile, verifier, dest_path, True) |
| return dest_path |
| |
| |
| def read_iat(filename, keyfile, verifier): |
| filepath = os.path.join(DATA_DIR, filename) |
| raw_iat = extract_iat_from_cose(keyfile, filepath, verifier, True) |
| return verifier.decode_and_validate_iat(raw_iat) |
| |
| |
| def create_and_read_iat(source_name, keyfile, verifier): |
| token_file = create_token(source_name, keyfile, verifier) |
| return read_iat(token_file, keyfile, verifier) |
| |
| |
| class TestIatVerifier(unittest.TestCase): |
| |
| def setUp(self): |
| self.config = VerifierConfiguration() |
| |
| def test_validate_signature(self): |
| verifier = PSAIoTProfile1TokenVerifier.get_verifier(self.config) |
| good_sig = create_token('valid-iat.yaml', KEYFILE, verifier) |
| bad_sig = create_token('valid-iat.yaml', KEYFILE_ALT, verifier) |
| |
| raw_iat = extract_iat_from_cose(KEYFILE, good_sig, verifier, True) |
| |
| with self.assertRaises(ValueError) as cm: |
| raw_iat = extract_iat_from_cose(KEYFILE, bad_sig, verifier, True) |
| |
| self.assertIn('Bad signature', cm.exception.args[0]) |
| |
| def test_validate_iat_structure(self): |
| keep_going_conf = VerifierConfiguration(keep_going=True) |
| |
| iat = create_and_read_iat('valid-iat.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| |
| with self.assertRaises(ValueError) as cm: |
| iat = create_and_read_iat('invalid-profile-id.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertIn('Invalid PROFILE_ID', cm.exception.args[0]) |
| |
| with self.assertRaises(ValueError) as cm: |
| iat = read_iat('malformed.cbor', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertIn('Bad COSE', cm.exception.args[0]) |
| |
| with self.assertRaises(ValueError) as cm: |
| iat = create_and_read_iat('missing-claim.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertIn('missing MANDATORY claim', cm.exception.args[0]) |
| |
| with self.assertRaises(ValueError) as cm: |
| iat = create_and_read_iat('submod-missing-claim.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertIn('missing MANDATORY claim', cm.exception.args[0]) |
| |
| with self.assertRaises(ValueError) as cm: |
| iat = create_and_read_iat('missing-sw-comps.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertIn('NO_MEASUREMENTS claim is not present', |
| cm.exception.args[0]) |
| |
| with self.assertLogs() as cm: |
| iat = create_and_read_iat('missing-signer-id.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertIn('Missing RECOMMENDED claim "SIGNER_ID" from sw_component', |
| cm.records[0].getMessage()) |
| |
| with self.assertLogs() as cm: |
| iat = create_and_read_iat('invalid-type-length.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(keep_going_conf)) |
| self.assertIn("Invalid PROFILE_ID: must be a(n) <class 'str'>: found <class 'int'>", |
| cm.records[0].getMessage()) |
| self.assertIn("Invalid SIGNER_ID: must be a(n) <class 'bytes'>: found <class 'str'>", |
| cm.records[1].getMessage()) |
| self.assertIn("Invalid SIGNER_ID length: must be at least 32 bytes, found 12 bytes", |
| cm.records[2].getMessage()) |
| self.assertIn("Invalid MEASUREMENT length: must be at least 32 bytes, found 28 bytes", |
| cm.records[3].getMessage()) |
| |
| with self.assertLogs() as cm: |
| iat = create_and_read_iat('invalid-hw-version.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(keep_going_conf)) |
| self.assertIn("Invalid HARDWARE_VERSION length; must be 13 digits, found 10 characters", |
| cm.records[0].getMessage()) |
| self.assertIn("Invalid digit at position 1", |
| cm.records[1].getMessage()) |
| self.assertIn("Invalid digit - at position 4", |
| cm.records[2].getMessage()) |
| self.assertIn("Invalid digit a at position 10", |
| cm.records[3].getMessage()) |
| |
| def test_binary_string_decoding(self): |
| iat = create_and_read_iat('valid-iat.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertEqual(iat['SECURITY_LIFECYCLE'], 'SL_SECURED') |
| |
| def test_security_lifecycle_decoding(self): |
| iat = create_and_read_iat('valid-iat.yaml', KEYFILE, PSAIoTProfile1TokenVerifier.get_verifier(self.config)) |
| self.assertEqual(iat['SECURITY_LIFECYCLE'], 'SL_SECURED') |