1a8e1175bSopenharmony_ci"""Knowledge about the PSA key store as implemented in Mbed TLS. 2a8e1175bSopenharmony_ci 3a8e1175bSopenharmony_ciNote that if you need to make a change that affects how keys are 4a8e1175bSopenharmony_cistored, this may indicate that the key store is changing in a 5a8e1175bSopenharmony_cibackward-incompatible way! Think carefully about backward compatibility 6a8e1175bSopenharmony_cibefore changing how test data is constructed or validated. 7a8e1175bSopenharmony_ci""" 8a8e1175bSopenharmony_ci 9a8e1175bSopenharmony_ci# Copyright The Mbed TLS Contributors 10a8e1175bSopenharmony_ci# SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later 11a8e1175bSopenharmony_ci# 12a8e1175bSopenharmony_ci 13a8e1175bSopenharmony_ciimport re 14a8e1175bSopenharmony_ciimport struct 15a8e1175bSopenharmony_cifrom typing import Dict, List, Optional, Set, Union 16a8e1175bSopenharmony_ciimport unittest 17a8e1175bSopenharmony_ci 18a8e1175bSopenharmony_cifrom . import c_build_helper 19a8e1175bSopenharmony_cifrom . import build_tree 20a8e1175bSopenharmony_ci 21a8e1175bSopenharmony_ci 22a8e1175bSopenharmony_ciclass Expr: 23a8e1175bSopenharmony_ci """Representation of a C expression with a known or knowable numerical value.""" 24a8e1175bSopenharmony_ci 25a8e1175bSopenharmony_ci def __init__(self, content: Union[int, str]): 26a8e1175bSopenharmony_ci if isinstance(content, int): 27a8e1175bSopenharmony_ci digits = 8 if content > 0xffff else 4 28a8e1175bSopenharmony_ci self.string = '{0:#0{1}x}'.format(content, digits + 2) 29a8e1175bSopenharmony_ci self.value_if_known = content #type: Optional[int] 30a8e1175bSopenharmony_ci else: 31a8e1175bSopenharmony_ci self.string = content 32a8e1175bSopenharmony_ci self.unknown_values.add(self.normalize(content)) 33a8e1175bSopenharmony_ci self.value_if_known = None 34a8e1175bSopenharmony_ci 35a8e1175bSopenharmony_ci value_cache = {} #type: Dict[str, int] 36a8e1175bSopenharmony_ci """Cache of known values of expressions.""" 37a8e1175bSopenharmony_ci 38a8e1175bSopenharmony_ci unknown_values = set() #type: Set[str] 39a8e1175bSopenharmony_ci """Expressions whose values are not present in `value_cache` yet.""" 40a8e1175bSopenharmony_ci 41a8e1175bSopenharmony_ci def update_cache(self) -> None: 42a8e1175bSopenharmony_ci """Update `value_cache` for expressions registered in `unknown_values`.""" 43a8e1175bSopenharmony_ci expressions = sorted(self.unknown_values) 44a8e1175bSopenharmony_ci includes = ['include'] 45a8e1175bSopenharmony_ci if build_tree.looks_like_tf_psa_crypto_root('.'): 46a8e1175bSopenharmony_ci includes.append('drivers/builtin/include') 47a8e1175bSopenharmony_ci values = c_build_helper.get_c_expression_values( 48a8e1175bSopenharmony_ci 'unsigned long', '%lu', 49a8e1175bSopenharmony_ci expressions, 50a8e1175bSopenharmony_ci header=""" 51a8e1175bSopenharmony_ci #include <psa/crypto.h> 52a8e1175bSopenharmony_ci """, 53a8e1175bSopenharmony_ci include_path=includes) #type: List[str] 54a8e1175bSopenharmony_ci for e, v in zip(expressions, values): 55a8e1175bSopenharmony_ci self.value_cache[e] = int(v, 0) 56a8e1175bSopenharmony_ci self.unknown_values.clear() 57a8e1175bSopenharmony_ci 58a8e1175bSopenharmony_ci @staticmethod 59a8e1175bSopenharmony_ci def normalize(string: str) -> str: 60a8e1175bSopenharmony_ci """Put the given C expression in a canonical form. 61a8e1175bSopenharmony_ci 62a8e1175bSopenharmony_ci This function is only intended to give correct results for the 63a8e1175bSopenharmony_ci relatively simple kind of C expression typically used with this 64a8e1175bSopenharmony_ci module. 65a8e1175bSopenharmony_ci """ 66a8e1175bSopenharmony_ci return re.sub(r'\s+', r'', string) 67a8e1175bSopenharmony_ci 68a8e1175bSopenharmony_ci def value(self) -> int: 69a8e1175bSopenharmony_ci """Return the numerical value of the expression.""" 70a8e1175bSopenharmony_ci if self.value_if_known is None: 71a8e1175bSopenharmony_ci if re.match(r'([0-9]+|0x[0-9a-f]+)\Z', self.string, re.I): 72a8e1175bSopenharmony_ci return int(self.string, 0) 73a8e1175bSopenharmony_ci normalized = self.normalize(self.string) 74a8e1175bSopenharmony_ci if normalized not in self.value_cache: 75a8e1175bSopenharmony_ci self.update_cache() 76a8e1175bSopenharmony_ci self.value_if_known = self.value_cache[normalized] 77a8e1175bSopenharmony_ci return self.value_if_known 78a8e1175bSopenharmony_ci 79a8e1175bSopenharmony_ciExprable = Union[str, int, Expr] 80a8e1175bSopenharmony_ci"""Something that can be converted to a C expression with a known numerical value.""" 81a8e1175bSopenharmony_ci 82a8e1175bSopenharmony_cidef as_expr(thing: Exprable) -> Expr: 83a8e1175bSopenharmony_ci """Return an `Expr` object for `thing`. 84a8e1175bSopenharmony_ci 85a8e1175bSopenharmony_ci If `thing` is already an `Expr` object, return it. Otherwise build a new 86a8e1175bSopenharmony_ci `Expr` object from `thing`. `thing` can be an integer or a string that 87a8e1175bSopenharmony_ci contains a C expression. 88a8e1175bSopenharmony_ci """ 89a8e1175bSopenharmony_ci if isinstance(thing, Expr): 90a8e1175bSopenharmony_ci return thing 91a8e1175bSopenharmony_ci else: 92a8e1175bSopenharmony_ci return Expr(thing) 93a8e1175bSopenharmony_ci 94a8e1175bSopenharmony_ci 95a8e1175bSopenharmony_ciclass Key: 96a8e1175bSopenharmony_ci """Representation of a PSA crypto key object and its storage encoding. 97a8e1175bSopenharmony_ci """ 98a8e1175bSopenharmony_ci 99a8e1175bSopenharmony_ci LATEST_VERSION = 0 100a8e1175bSopenharmony_ci """The latest version of the storage format.""" 101a8e1175bSopenharmony_ci 102a8e1175bSopenharmony_ci def __init__(self, *, 103a8e1175bSopenharmony_ci version: Optional[int] = None, 104a8e1175bSopenharmony_ci id: Optional[int] = None, #pylint: disable=redefined-builtin 105a8e1175bSopenharmony_ci lifetime: Exprable = 'PSA_KEY_LIFETIME_PERSISTENT', 106a8e1175bSopenharmony_ci type: Exprable, #pylint: disable=redefined-builtin 107a8e1175bSopenharmony_ci bits: int, 108a8e1175bSopenharmony_ci usage: Exprable, alg: Exprable, alg2: Exprable, 109a8e1175bSopenharmony_ci material: bytes #pylint: disable=used-before-assignment 110a8e1175bSopenharmony_ci ) -> None: 111a8e1175bSopenharmony_ci self.version = self.LATEST_VERSION if version is None else version 112a8e1175bSopenharmony_ci self.id = id #pylint: disable=invalid-name #type: Optional[int] 113a8e1175bSopenharmony_ci self.lifetime = as_expr(lifetime) #type: Expr 114a8e1175bSopenharmony_ci self.type = as_expr(type) #type: Expr 115a8e1175bSopenharmony_ci self.bits = bits #type: int 116a8e1175bSopenharmony_ci self.usage = as_expr(usage) #type: Expr 117a8e1175bSopenharmony_ci self.alg = as_expr(alg) #type: Expr 118a8e1175bSopenharmony_ci self.alg2 = as_expr(alg2) #type: Expr 119a8e1175bSopenharmony_ci self.material = material #type: bytes 120a8e1175bSopenharmony_ci 121a8e1175bSopenharmony_ci MAGIC = b'PSA\000KEY\000' 122a8e1175bSopenharmony_ci 123a8e1175bSopenharmony_ci @staticmethod 124a8e1175bSopenharmony_ci def pack( 125a8e1175bSopenharmony_ci fmt: str, 126a8e1175bSopenharmony_ci *args: Union[int, Expr] 127a8e1175bSopenharmony_ci ) -> bytes: #pylint: disable=used-before-assignment 128a8e1175bSopenharmony_ci """Pack the given arguments into a byte string according to the given format. 129a8e1175bSopenharmony_ci 130a8e1175bSopenharmony_ci This function is similar to `struct.pack`, but with the following differences: 131a8e1175bSopenharmony_ci * All integer values are encoded with standard sizes and in 132a8e1175bSopenharmony_ci little-endian representation. `fmt` must not include an endianness 133a8e1175bSopenharmony_ci prefix. 134a8e1175bSopenharmony_ci * Arguments can be `Expr` objects instead of integers. 135a8e1175bSopenharmony_ci * Only integer-valued elements are supported. 136a8e1175bSopenharmony_ci """ 137a8e1175bSopenharmony_ci return struct.pack('<' + fmt, # little-endian, standard sizes 138a8e1175bSopenharmony_ci *[arg.value() if isinstance(arg, Expr) else arg 139a8e1175bSopenharmony_ci for arg in args]) 140a8e1175bSopenharmony_ci 141a8e1175bSopenharmony_ci def bytes(self) -> bytes: 142a8e1175bSopenharmony_ci """Return the representation of the key in storage as a byte array. 143a8e1175bSopenharmony_ci 144a8e1175bSopenharmony_ci This is the content of the PSA storage file. When PSA storage is 145a8e1175bSopenharmony_ci implemented over stdio files, this does not include any wrapping made 146a8e1175bSopenharmony_ci by the PSA-storage-over-stdio-file implementation. 147a8e1175bSopenharmony_ci 148a8e1175bSopenharmony_ci Note that if you need to make a change in this function, 149a8e1175bSopenharmony_ci this may indicate that the key store is changing in a 150a8e1175bSopenharmony_ci backward-incompatible way! Think carefully about backward 151a8e1175bSopenharmony_ci compatibility before making any change here. 152a8e1175bSopenharmony_ci """ 153a8e1175bSopenharmony_ci header = self.MAGIC + self.pack('L', self.version) 154a8e1175bSopenharmony_ci if self.version == 0: 155a8e1175bSopenharmony_ci attributes = self.pack('LHHLLL', 156a8e1175bSopenharmony_ci self.lifetime, self.type, self.bits, 157a8e1175bSopenharmony_ci self.usage, self.alg, self.alg2) 158a8e1175bSopenharmony_ci material = self.pack('L', len(self.material)) + self.material 159a8e1175bSopenharmony_ci else: 160a8e1175bSopenharmony_ci raise NotImplementedError 161a8e1175bSopenharmony_ci return header + attributes + material 162a8e1175bSopenharmony_ci 163a8e1175bSopenharmony_ci def hex(self) -> str: 164a8e1175bSopenharmony_ci """Return the representation of the key as a hexadecimal string. 165a8e1175bSopenharmony_ci 166a8e1175bSopenharmony_ci This is the hexadecimal representation of `self.bytes`. 167a8e1175bSopenharmony_ci """ 168a8e1175bSopenharmony_ci return self.bytes().hex() 169a8e1175bSopenharmony_ci 170a8e1175bSopenharmony_ci def location_value(self) -> int: 171a8e1175bSopenharmony_ci """The numerical value of the location encoded in the key's lifetime.""" 172a8e1175bSopenharmony_ci return self.lifetime.value() >> 8 173a8e1175bSopenharmony_ci 174a8e1175bSopenharmony_ci 175a8e1175bSopenharmony_ciclass TestKey(unittest.TestCase): 176a8e1175bSopenharmony_ci # pylint: disable=line-too-long 177a8e1175bSopenharmony_ci """A few smoke tests for the functionality of the `Key` class.""" 178a8e1175bSopenharmony_ci 179a8e1175bSopenharmony_ci def test_numerical(self): 180a8e1175bSopenharmony_ci key = Key(version=0, 181a8e1175bSopenharmony_ci id=1, lifetime=0x00000001, 182a8e1175bSopenharmony_ci type=0x2400, bits=128, 183a8e1175bSopenharmony_ci usage=0x00000300, alg=0x05500200, alg2=0x04c01000, 184a8e1175bSopenharmony_ci material=b'@ABCDEFGHIJKLMNO') 185a8e1175bSopenharmony_ci expected_hex = '505341004b45590000000000010000000024800000030000000250050010c00410000000404142434445464748494a4b4c4d4e4f' 186a8e1175bSopenharmony_ci self.assertEqual(key.bytes(), bytes.fromhex(expected_hex)) 187a8e1175bSopenharmony_ci self.assertEqual(key.hex(), expected_hex) 188a8e1175bSopenharmony_ci 189a8e1175bSopenharmony_ci def test_names(self): 190a8e1175bSopenharmony_ci length = 0xfff8 // 8 # PSA_MAX_KEY_BITS in bytes 191a8e1175bSopenharmony_ci key = Key(version=0, 192a8e1175bSopenharmony_ci id=1, lifetime='PSA_KEY_LIFETIME_PERSISTENT', 193a8e1175bSopenharmony_ci type='PSA_KEY_TYPE_RAW_DATA', bits=length*8, 194a8e1175bSopenharmony_ci usage=0, alg=0, alg2=0, 195a8e1175bSopenharmony_ci material=b'\x00' * length) 196a8e1175bSopenharmony_ci expected_hex = '505341004b45590000000000010000000110f8ff000000000000000000000000ff1f0000' + '00' * length 197a8e1175bSopenharmony_ci self.assertEqual(key.bytes(), bytes.fromhex(expected_hex)) 198a8e1175bSopenharmony_ci self.assertEqual(key.hex(), expected_hex) 199a8e1175bSopenharmony_ci 200a8e1175bSopenharmony_ci def test_defaults(self): 201a8e1175bSopenharmony_ci key = Key(type=0x1001, bits=8, 202a8e1175bSopenharmony_ci usage=0, alg=0, alg2=0, 203a8e1175bSopenharmony_ci material=b'\x2a') 204a8e1175bSopenharmony_ci expected_hex = '505341004b455900000000000100000001100800000000000000000000000000010000002a' 205a8e1175bSopenharmony_ci self.assertEqual(key.bytes(), bytes.fromhex(expected_hex)) 206a8e1175bSopenharmony_ci self.assertEqual(key.hex(), expected_hex) 207