blob: 737760fd426b95c9f69cdc48c7b2d6eedaecdeb9 [file] [log] [blame]
Gilles Peskinee0094482021-02-17 14:34:37 +01001"""Knowledge about the PSA key store as implemented in Mbed TLS.
Gilles Peskine76851ae2022-06-20 19:10:35 +02002
3Note that if you need to make a change that affects how keys are
4stored, this may indicate that the key store is changing in a
5backward-incompatible way! Think carefully about backward compatibility
6before changing how test data is constructed or validated.
Gilles Peskinee0094482021-02-17 14:34:37 +01007"""
8
9# Copyright The Mbed TLS Contributors
10# SPDX-License-Identifier: Apache-2.0
11#
12# Licensed under the Apache License, Version 2.0 (the "License"); you may
13# not use this file except in compliance with the License.
14# You may obtain a copy of the License at
15#
16# http://www.apache.org/licenses/LICENSE-2.0
17#
18# Unless required by applicable law or agreed to in writing, software
19# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
20# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21# See the License for the specific language governing permissions and
22# limitations under the License.
23
24import re
25import struct
Gilles Peskine23523962021-03-10 01:32:38 +010026from typing import Dict, List, Optional, Set, Union
Gilles Peskinee0094482021-02-17 14:34:37 +010027import unittest
28
Gilles Peskine15997bd2022-09-16 22:35:18 +020029from . import c_build_helper
David Horstmann42f42f42023-07-18 17:53:01 +010030from . import build_tree
Gilles Peskine23523962021-03-10 01:32:38 +010031
Gilles Peskinee0094482021-02-17 14:34:37 +010032
33class Expr:
34 """Representation of a C expression with a known or knowable numerical value."""
Gilles Peskine23523962021-03-10 01:32:38 +010035
Gilles Peskinee0094482021-02-17 14:34:37 +010036 def __init__(self, content: Union[int, str]):
37 if isinstance(content, int):
38 digits = 8 if content > 0xffff else 4
39 self.string = '{0:#0{1}x}'.format(content, digits + 2)
40 self.value_if_known = content #type: Optional[int]
41 else:
42 self.string = content
Gilles Peskine23523962021-03-10 01:32:38 +010043 self.unknown_values.add(self.normalize(content))
Gilles Peskinee0094482021-02-17 14:34:37 +010044 self.value_if_known = None
45
Gilles Peskine23523962021-03-10 01:32:38 +010046 value_cache = {} #type: Dict[str, int]
47 """Cache of known values of expressions."""
48
49 unknown_values = set() #type: Set[str]
50 """Expressions whose values are not present in `value_cache` yet."""
Gilles Peskinee0094482021-02-17 14:34:37 +010051
52 def update_cache(self) -> None:
Gilles Peskine23523962021-03-10 01:32:38 +010053 """Update `value_cache` for expressions registered in `unknown_values`."""
54 expressions = sorted(self.unknown_values)
David Horstmann42f42f42023-07-18 17:53:01 +010055 includes = ['include']
Ronald Cron070e8652023-10-09 10:25:45 +020056 if build_tree.looks_like_tf_psa_crypto_root('.'):
David Horstmann42f42f42023-07-18 17:53:01 +010057 includes.append('drivers/builtin/include')
Gilles Peskine23523962021-03-10 01:32:38 +010058 values = c_build_helper.get_c_expression_values(
59 'unsigned long', '%lu',
60 expressions,
61 header="""
62 #include <psa/crypto.h>
63 """,
David Horstmann42f42f42023-07-18 17:53:01 +010064 include_path=includes) #type: List[str]
Gilles Peskine23523962021-03-10 01:32:38 +010065 for e, v in zip(expressions, values):
66 self.value_cache[e] = int(v, 0)
67 self.unknown_values.clear()
Gilles Peskinee0094482021-02-17 14:34:37 +010068
69 @staticmethod
70 def normalize(string: str) -> str:
71 """Put the given C expression in a canonical form.
72
73 This function is only intended to give correct results for the
74 relatively simple kind of C expression typically used with this
75 module.
76 """
77 return re.sub(r'\s+', r'', string)
78
79 def value(self) -> int:
80 """Return the numerical value of the expression."""
81 if self.value_if_known is None:
82 if re.match(r'([0-9]+|0x[0-9a-f]+)\Z', self.string, re.I):
83 return int(self.string, 0)
84 normalized = self.normalize(self.string)
85 if normalized not in self.value_cache:
86 self.update_cache()
87 self.value_if_known = self.value_cache[normalized]
88 return self.value_if_known
89
90Exprable = Union[str, int, Expr]
91"""Something that can be converted to a C expression with a known numerical value."""
92
93def as_expr(thing: Exprable) -> Expr:
94 """Return an `Expr` object for `thing`.
95
96 If `thing` is already an `Expr` object, return it. Otherwise build a new
97 `Expr` object from `thing`. `thing` can be an integer or a string that
98 contains a C expression.
99 """
100 if isinstance(thing, Expr):
101 return thing
102 else:
103 return Expr(thing)
104
105
106class Key:
107 """Representation of a PSA crypto key object and its storage encoding.
108 """
109
110 LATEST_VERSION = 0
111 """The latest version of the storage format."""
112
113 def __init__(self, *,
114 version: Optional[int] = None,
115 id: Optional[int] = None, #pylint: disable=redefined-builtin
116 lifetime: Exprable = 'PSA_KEY_LIFETIME_PERSISTENT',
117 type: Exprable, #pylint: disable=redefined-builtin
118 bits: int,
119 usage: Exprable, alg: Exprable, alg2: Exprable,
120 material: bytes #pylint: disable=used-before-assignment
121 ) -> None:
122 self.version = self.LATEST_VERSION if version is None else version
123 self.id = id #pylint: disable=invalid-name #type: Optional[int]
124 self.lifetime = as_expr(lifetime) #type: Expr
125 self.type = as_expr(type) #type: Expr
126 self.bits = bits #type: int
127 self.usage = as_expr(usage) #type: Expr
128 self.alg = as_expr(alg) #type: Expr
129 self.alg2 = as_expr(alg2) #type: Expr
130 self.material = material #type: bytes
131
132 MAGIC = b'PSA\000KEY\000'
133
134 @staticmethod
135 def pack(
136 fmt: str,
137 *args: Union[int, Expr]
138 ) -> bytes: #pylint: disable=used-before-assignment
139 """Pack the given arguments into a byte string according to the given format.
140
141 This function is similar to `struct.pack`, but with the following differences:
142 * All integer values are encoded with standard sizes and in
143 little-endian representation. `fmt` must not include an endianness
144 prefix.
145 * Arguments can be `Expr` objects instead of integers.
146 * Only integer-valued elements are supported.
147 """
148 return struct.pack('<' + fmt, # little-endian, standard sizes
149 *[arg.value() if isinstance(arg, Expr) else arg
150 for arg in args])
151
152 def bytes(self) -> bytes:
153 """Return the representation of the key in storage as a byte array.
154
155 This is the content of the PSA storage file. When PSA storage is
156 implemented over stdio files, this does not include any wrapping made
157 by the PSA-storage-over-stdio-file implementation.
Gilles Peskine76851ae2022-06-20 19:10:35 +0200158
159 Note that if you need to make a change in this function,
160 this may indicate that the key store is changing in a
161 backward-incompatible way! Think carefully about backward
162 compatibility before making any change here.
Gilles Peskinee0094482021-02-17 14:34:37 +0100163 """
164 header = self.MAGIC + self.pack('L', self.version)
165 if self.version == 0:
166 attributes = self.pack('LHHLLL',
167 self.lifetime, self.type, self.bits,
168 self.usage, self.alg, self.alg2)
169 material = self.pack('L', len(self.material)) + self.material
170 else:
171 raise NotImplementedError
172 return header + attributes + material
173
174 def hex(self) -> str:
175 """Return the representation of the key as a hexadecimal string.
176
177 This is the hexadecimal representation of `self.bytes`.
178 """
179 return self.bytes().hex()
180
Gilles Peskineeb7bdaa2021-04-21 22:05:34 +0200181 def location_value(self) -> int:
182 """The numerical value of the location encoded in the key's lifetime."""
183 return self.lifetime.value() >> 8
184
Gilles Peskinee0094482021-02-17 14:34:37 +0100185
186class TestKey(unittest.TestCase):
187 # pylint: disable=line-too-long
188 """A few smoke tests for the functionality of the `Key` class."""
189
190 def test_numerical(self):
191 key = Key(version=0,
192 id=1, lifetime=0x00000001,
193 type=0x2400, bits=128,
194 usage=0x00000300, alg=0x05500200, alg2=0x04c01000,
195 material=b'@ABCDEFGHIJKLMNO')
196 expected_hex = '505341004b45590000000000010000000024800000030000000250050010c00410000000404142434445464748494a4b4c4d4e4f'
197 self.assertEqual(key.bytes(), bytes.fromhex(expected_hex))
198 self.assertEqual(key.hex(), expected_hex)
199
200 def test_names(self):
201 length = 0xfff8 // 8 # PSA_MAX_KEY_BITS in bytes
202 key = Key(version=0,
203 id=1, lifetime='PSA_KEY_LIFETIME_PERSISTENT',
204 type='PSA_KEY_TYPE_RAW_DATA', bits=length*8,
205 usage=0, alg=0, alg2=0,
206 material=b'\x00' * length)
207 expected_hex = '505341004b45590000000000010000000110f8ff000000000000000000000000ff1f0000' + '00' * length
208 self.assertEqual(key.bytes(), bytes.fromhex(expected_hex))
209 self.assertEqual(key.hex(), expected_hex)
210
211 def test_defaults(self):
212 key = Key(type=0x1001, bits=8,
213 usage=0, alg=0, alg2=0,
214 material=b'\x2a')
215 expected_hex = '505341004b455900000000000100000001100800000000000000000000000000010000002a'
216 self.assertEqual(key.bytes(), bytes.fromhex(expected_hex))
217 self.assertEqual(key.hex(), expected_hex)