mirror of
https://github.com/Mbed-TLS/mbedtls.git
synced 2025-08-01 10:06:53 +03:00
Merge pull request #5644 from gilles-peskine-arm/psa-storage-format-test-exercise
PSA storage format: exercise key
This commit is contained in:
@ -20,11 +20,34 @@ This module is entirely based on the PSA API.
|
||||
|
||||
import enum
|
||||
import re
|
||||
from typing import Dict, Iterable, Optional, Pattern, Tuple
|
||||
from typing import FrozenSet, Iterable, List, Optional, Tuple
|
||||
|
||||
from mbedtls_dev.asymmetric_key_data import ASYMMETRIC_KEY_DATA
|
||||
|
||||
|
||||
def short_expression(original: str, level: int = 0) -> str:
|
||||
"""Abbreviate the expression, keeping it human-readable.
|
||||
|
||||
If `level` is 0, just remove parts that are implicit from context,
|
||||
such as a leading ``PSA_KEY_TYPE_``.
|
||||
For larger values of `level`, also abbreviate some names in an
|
||||
unambiguous, but ad hoc way.
|
||||
"""
|
||||
short = original
|
||||
short = re.sub(r'\bPSA_(?:ALG|ECC_FAMILY|KEY_[A-Z]+)_', r'', short)
|
||||
short = re.sub(r' +', r'', short)
|
||||
if level >= 1:
|
||||
short = re.sub(r'PUBLIC_KEY\b', r'PUB', short)
|
||||
short = re.sub(r'KEY_PAIR\b', r'PAIR', short)
|
||||
short = re.sub(r'\bBRAINPOOL_P', r'BP', short)
|
||||
short = re.sub(r'\bMONTGOMERY\b', r'MGM', short)
|
||||
short = re.sub(r'AEAD_WITH_SHORTENED_TAG\b', r'AEAD_SHORT', short)
|
||||
short = re.sub(r'\bDETERMINISTIC_', r'DET_', short)
|
||||
short = re.sub(r'\bKEY_AGREEMENT\b', r'KA', short)
|
||||
short = re.sub(r'_PSK_TO_MS\b', r'_PSK2MS', short)
|
||||
return short
|
||||
|
||||
|
||||
BLOCK_CIPHERS = frozenset(['AES', 'ARIA', 'CAMELLIA', 'DES'])
|
||||
BLOCK_MAC_MODES = frozenset(['CBC_MAC', 'CMAC'])
|
||||
BLOCK_CIPHER_MODES = frozenset([
|
||||
@ -104,6 +127,13 @@ class KeyType:
|
||||
`self.name`.
|
||||
"""
|
||||
|
||||
def short_expression(self, level: int = 0) -> str:
|
||||
"""Abbreviate the expression, keeping it human-readable.
|
||||
|
||||
See `crypto_knowledge.short_expression`.
|
||||
"""
|
||||
return short_expression(self.expression, level=level)
|
||||
|
||||
def is_public(self) -> bool:
|
||||
"""Whether the key type is for public keys."""
|
||||
return self.name.endswith('_PUBLIC_KEY')
|
||||
@ -178,35 +208,31 @@ class KeyType:
|
||||
return b''.join([self.DATA_BLOCK] * (length // len(self.DATA_BLOCK)) +
|
||||
[self.DATA_BLOCK[:length % len(self.DATA_BLOCK)]])
|
||||
|
||||
KEY_TYPE_FOR_SIGNATURE = {
|
||||
'PSA_KEY_USAGE_SIGN_HASH': re.compile('.*KEY_PAIR'),
|
||||
'PSA_KEY_USAGE_VERIFY_HASH': re.compile('.*KEY.*')
|
||||
} #type: Dict[str, Pattern]
|
||||
"""Use a regexp to determine key types for which signature is possible
|
||||
when using the actual usage flag.
|
||||
"""
|
||||
def is_valid_for_signature(self, usage: str) -> bool:
|
||||
"""Determine if the key type is compatible with the specified
|
||||
signitute type.
|
||||
|
||||
"""
|
||||
# This is just temporaly solution for the implicit usage flags.
|
||||
return re.match(self.KEY_TYPE_FOR_SIGNATURE[usage], self.name) is not None
|
||||
|
||||
def can_do(self, alg: 'Algorithm') -> bool:
|
||||
"""Whether this key type can be used for operations with the given algorithm.
|
||||
|
||||
This function does not currently handle key derivation or PAKE.
|
||||
"""
|
||||
#pylint: disable=too-many-return-statements
|
||||
#pylint: disable=too-many-branches,too-many-return-statements
|
||||
if alg.is_wildcard:
|
||||
return False
|
||||
if alg.is_invalid_truncation():
|
||||
return False
|
||||
if self.head == 'HMAC' and alg.head == 'HMAC':
|
||||
return True
|
||||
if self.head == 'DES':
|
||||
# 64-bit block ciphers only allow a reduced set of modes.
|
||||
return alg.head in [
|
||||
'CBC_NO_PADDING', 'CBC_PKCS7',
|
||||
'ECB_NO_PADDING',
|
||||
]
|
||||
if self.head in BLOCK_CIPHERS and \
|
||||
alg.head in frozenset.union(BLOCK_MAC_MODES,
|
||||
BLOCK_CIPHER_MODES,
|
||||
BLOCK_AEAD_MODES):
|
||||
if alg.head in ['CMAC', 'OFB'] and \
|
||||
self.head in ['ARIA', 'CAMELLIA']:
|
||||
return False # not implemented in Mbed TLS
|
||||
return True
|
||||
if self.head == 'CHACHA20' and alg.head == 'CHACHA20_POLY1305':
|
||||
return True
|
||||
@ -215,6 +241,13 @@ class KeyType:
|
||||
return True
|
||||
if self.head == 'RSA' and alg.head.startswith('RSA_'):
|
||||
return True
|
||||
if alg.category == AlgorithmCategory.KEY_AGREEMENT and \
|
||||
self.is_public():
|
||||
# The PSA API does not use public key objects in key agreement
|
||||
# operations: it imports the public key as a formatted byte string.
|
||||
# So a public key object with a key agreement algorithm is not
|
||||
# a valid combination.
|
||||
return False
|
||||
if self.head == 'ECC':
|
||||
assert self.params is not None
|
||||
eccc = EllipticCurveCategory.from_family(self.params[0])
|
||||
@ -391,11 +424,113 @@ class Algorithm:
|
||||
# Assume kdf_alg is either a valid KDF or 0.
|
||||
return not re.match(r'(?:0[Xx])?0+\s*\Z', kdf_alg)
|
||||
|
||||
|
||||
def short_expression(self, level: int = 0) -> str:
|
||||
"""Abbreviate the expression, keeping it human-readable.
|
||||
|
||||
See `crypto_knowledge.short_expression`.
|
||||
"""
|
||||
return short_expression(self.expression, level=level)
|
||||
|
||||
HASH_LENGTH = {
|
||||
'PSA_ALG_MD5': 16,
|
||||
'PSA_ALG_SHA_1': 20,
|
||||
}
|
||||
HASH_LENGTH_BITS_RE = re.compile(r'([0-9]+)\Z')
|
||||
@classmethod
|
||||
def hash_length(cls, alg: str) -> int:
|
||||
"""The length of the given hash algorithm, in bytes."""
|
||||
if alg in cls.HASH_LENGTH:
|
||||
return cls.HASH_LENGTH[alg]
|
||||
m = cls.HASH_LENGTH_BITS_RE.search(alg)
|
||||
if m:
|
||||
return int(m.group(1)) // 8
|
||||
raise ValueError('Unknown hash length for ' + alg)
|
||||
|
||||
PERMITTED_TAG_LENGTHS = {
|
||||
'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]),
|
||||
'PSA_ALG_CHACHA20_POLY1305': frozenset([16]),
|
||||
'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]),
|
||||
}
|
||||
MAC_LENGTH = {
|
||||
'PSA_ALG_CBC_MAC': 16, # actually the block cipher length
|
||||
'PSA_ALG_CMAC': 16, # actually the block cipher length
|
||||
}
|
||||
HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z')
|
||||
@classmethod
|
||||
def permitted_truncations(cls, base: str) -> FrozenSet[int]:
|
||||
"""Permitted output lengths for the given MAC or AEAD base algorithm.
|
||||
|
||||
For a MAC algorithm, this is the set of truncation lengths that
|
||||
Mbed TLS supports.
|
||||
For an AEAD algorithm, this is the set of truncation lengths that
|
||||
are permitted by the algorithm specification.
|
||||
"""
|
||||
if base in cls.PERMITTED_TAG_LENGTHS:
|
||||
return cls.PERMITTED_TAG_LENGTHS[base]
|
||||
max_length = cls.MAC_LENGTH.get(base, None)
|
||||
if max_length is None:
|
||||
m = cls.HMAC_RE.match(base)
|
||||
if m:
|
||||
max_length = cls.hash_length(m.group(1))
|
||||
if max_length is None:
|
||||
raise ValueError('Unknown permitted lengths for ' + base)
|
||||
return frozenset(range(4, max_length + 1))
|
||||
|
||||
TRUNCATED_ALG_RE = re.compile(
|
||||
r'(?P<face>PSA_ALG_(?:AEAD_WITH_SHORTENED_TAG|TRUNCATED_MAC))'
|
||||
r'\((?P<base>.*),'
|
||||
r'(?P<length>0[Xx][0-9A-Fa-f]+|[1-9][0-9]*|0[0-7]*)[LUlu]*\)\Z')
|
||||
def is_invalid_truncation(self) -> bool:
|
||||
"""False for a MAC or AEAD algorithm truncated to an invalid length.
|
||||
|
||||
True for a MAC or AEAD algorithm truncated to a valid length or to
|
||||
a length that cannot be determined. True for anything other than
|
||||
a truncated MAC or AEAD.
|
||||
"""
|
||||
m = self.TRUNCATED_ALG_RE.match(self.expression)
|
||||
if m:
|
||||
base = m.group('base')
|
||||
to_length = int(m.group('length'), 0)
|
||||
permitted_lengths = self.permitted_truncations(base)
|
||||
if to_length not in permitted_lengths:
|
||||
return True
|
||||
return False
|
||||
|
||||
def can_do(self, category: AlgorithmCategory) -> bool:
|
||||
"""Whether this algorithm fits the specified operation category."""
|
||||
"""Whether this algorithm can perform operations in the given category.
|
||||
"""
|
||||
if category == self.category:
|
||||
return True
|
||||
if category == AlgorithmCategory.KEY_DERIVATION and \
|
||||
self.is_key_agreement_with_derivation():
|
||||
return True
|
||||
return False
|
||||
|
||||
def usage_flags(self, public: bool = False) -> List[str]:
|
||||
"""The list of usage flags describing operations that can perform this algorithm.
|
||||
|
||||
If public is true, only return public-key operations, not private-key operations.
|
||||
"""
|
||||
if self.category == AlgorithmCategory.HASH:
|
||||
flags = []
|
||||
elif self.category == AlgorithmCategory.MAC:
|
||||
flags = ['SIGN_HASH', 'SIGN_MESSAGE',
|
||||
'VERIFY_HASH', 'VERIFY_MESSAGE']
|
||||
elif self.category == AlgorithmCategory.CIPHER or \
|
||||
self.category == AlgorithmCategory.AEAD:
|
||||
flags = ['DECRYPT', 'ENCRYPT']
|
||||
elif self.category == AlgorithmCategory.SIGN:
|
||||
flags = ['VERIFY_HASH', 'VERIFY_MESSAGE']
|
||||
if not public:
|
||||
flags += ['SIGN_HASH', 'SIGN_MESSAGE']
|
||||
elif self.category == AlgorithmCategory.ASYMMETRIC_ENCRYPTION:
|
||||
flags = ['ENCRYPT']
|
||||
if not public:
|
||||
flags += ['DECRYPT']
|
||||
elif self.category == AlgorithmCategory.KEY_DERIVATION or \
|
||||
self.category == AlgorithmCategory.KEY_AGREEMENT:
|
||||
flags = ['DERIVE']
|
||||
else:
|
||||
raise AlgorithmNotRecognized(self.expression)
|
||||
return ['PSA_KEY_USAGE_' + flag for flag in flags]
|
||||
|
@ -186,7 +186,7 @@ class PSAMacroEnumerator:
|
||||
for value in argument_lists[i][1:]:
|
||||
arguments[i] = value
|
||||
yield self._format_arguments(name, arguments)
|
||||
arguments[i] = argument_lists[0][0]
|
||||
arguments[i] = argument_lists[i][0]
|
||||
except BaseException as e:
|
||||
raise Exception('distribute_arguments({})'.format(name)) from e
|
||||
|
||||
@ -400,10 +400,26 @@ enumerate
|
||||
'other_algorithm': [],
|
||||
'lifetime': [self.lifetimes],
|
||||
} #type: Dict[str, List[Set[str]]]
|
||||
self.arguments_for['mac_length'] += ['1', '63']
|
||||
self.arguments_for['min_mac_length'] += ['1', '63']
|
||||
self.arguments_for['tag_length'] += ['1', '63']
|
||||
self.arguments_for['min_tag_length'] += ['1', '63']
|
||||
mac_lengths = [str(n) for n in [
|
||||
1, # minimum expressible
|
||||
4, # minimum allowed by policy
|
||||
13, # an odd size in a plausible range
|
||||
14, # an even non-power-of-two size in a plausible range
|
||||
16, # same as full size for at least one algorithm
|
||||
63, # maximum expressible
|
||||
]]
|
||||
self.arguments_for['mac_length'] += mac_lengths
|
||||
self.arguments_for['min_mac_length'] += mac_lengths
|
||||
aead_lengths = [str(n) for n in [
|
||||
1, # minimum expressible
|
||||
4, # minimum allowed by policy
|
||||
13, # an odd size in a plausible range
|
||||
14, # an even non-power-of-two size in a plausible range
|
||||
16, # same as full size for at least one algorithm
|
||||
63, # maximum expressible
|
||||
]]
|
||||
self.arguments_for['tag_length'] += aead_lengths
|
||||
self.arguments_for['min_tag_length'] += aead_lengths
|
||||
|
||||
def add_numerical_values(self) -> None:
|
||||
"""Add numerical values that are not supported to the known identifiers."""
|
||||
|
Reference in New Issue
Block a user