Don't exercise OAEP with small key and large hash
RSA-OAEP requires the key to be larger than a function of the hash size. Ideally such combinations would be detected as a key/algorithm incompatibility. However key/algorithm compatibility is currently tested between the key type and the algorithm without considering the key size, and this is inconvenient to change. So as a workaround, dispense OAEP-with-too-small-hash from exercising, without including it in the automatic operation-failure test generation. Signed-off-by: Gilles Peskine <Gilles.Peskine@arm.com>
This commit is contained in:
parent
d586b82e12
commit
4bd90dc6b1
2 changed files with 51 additions and 11 deletions
|
@ -213,7 +213,7 @@ class KeyType:
|
||||||
|
|
||||||
This function does not currently handle key derivation or PAKE.
|
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:
|
if alg.is_wildcard:
|
||||||
return False
|
return False
|
||||||
if alg.is_invalid_truncation():
|
if alg.is_invalid_truncation():
|
||||||
|
@ -425,28 +425,41 @@ class Algorithm:
|
||||||
"""
|
"""
|
||||||
return short_expression(self.expression, level=level)
|
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 = {
|
PERMITTED_TAG_LENGTHS = {
|
||||||
'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]),
|
'PSA_ALG_CCM': frozenset([4, 6, 8, 10, 12, 14, 16]),
|
||||||
'PSA_ALG_CHACHA20_POLY1305': frozenset([16]),
|
'PSA_ALG_CHACHA20_POLY1305': frozenset([16]),
|
||||||
'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]),
|
'PSA_ALG_GCM': frozenset([4, 8, 12, 13, 14, 15, 16]),
|
||||||
}
|
}
|
||||||
MAC_LENGTH = {
|
MAC_LENGTH = {
|
||||||
'PSA_ALG_CBC_MAC': 16,
|
'PSA_ALG_CBC_MAC': 16, # actually the block cipher length
|
||||||
'PSA_ALG_CMAC': 16,
|
'PSA_ALG_CMAC': 16, # actually the block cipher length
|
||||||
'PSA_ALG_HMAC(PSA_ALG_MD5)': 16,
|
|
||||||
'PSA_ALG_HMAC(PSA_ALG_SHA_1)': 20,
|
|
||||||
}
|
}
|
||||||
HMAC_WITH_NOMINAL_LENGTH_RE = re.compile(r'PSA_ALG_HMAC\(\w+([0-9])+\)\Z')
|
HMAC_RE = re.compile(r'PSA_ALG_HMAC\((.*)\)\Z')
|
||||||
@classmethod
|
@classmethod
|
||||||
def mac_or_tag_length(cls, base: str) -> FrozenSet[int]:
|
def mac_or_tag_lengths(cls, base: str) -> FrozenSet[int]:
|
||||||
"""Return the set of permitted lengths for the given MAC or AEAD tag."""
|
"""Return the set of permitted lengths for the given MAC or AEAD tag."""
|
||||||
if base in cls.PERMITTED_TAG_LENGTHS:
|
if base in cls.PERMITTED_TAG_LENGTHS:
|
||||||
return cls.PERMITTED_TAG_LENGTHS[base]
|
return cls.PERMITTED_TAG_LENGTHS[base]
|
||||||
max_length = cls.MAC_LENGTH.get(base, None)
|
max_length = cls.MAC_LENGTH.get(base, None)
|
||||||
if max_length is None:
|
if max_length is None:
|
||||||
m = cls.HMAC_WITH_NOMINAL_LENGTH_RE.match(base)
|
m = cls.HMAC_RE.match(base)
|
||||||
if m:
|
if m:
|
||||||
max_length = int(m.group(1)) // 8
|
max_length = cls.hash_length(m.group(1))
|
||||||
if max_length is None:
|
if max_length is None:
|
||||||
raise ValueError('Unknown permitted lengths for ' + base)
|
raise ValueError('Unknown permitted lengths for ' + base)
|
||||||
return frozenset(range(4, max_length + 1))
|
return frozenset(range(4, max_length + 1))
|
||||||
|
@ -466,7 +479,7 @@ class Algorithm:
|
||||||
if m:
|
if m:
|
||||||
base = m.group('base')
|
base = m.group('base')
|
||||||
to_length = int(m.group('length'), 0)
|
to_length = int(m.group('length'), 0)
|
||||||
permitted_lengths = self.mac_or_tag_length(base)
|
permitted_lengths = self.mac_or_tag_lengths(base)
|
||||||
if to_length not in permitted_lengths:
|
if to_length not in permitted_lengths:
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
|
@ -518,6 +518,32 @@ class StorageFormat:
|
||||||
self.version = version #type: int
|
self.version = version #type: int
|
||||||
self.forward = forward #type: bool
|
self.forward = forward #type: bool
|
||||||
|
|
||||||
|
RSA_OAEP_RE = re.compile(r'PSA_ALG_RSA_OAEP\((.*)\)\Z')
|
||||||
|
@classmethod
|
||||||
|
def valid_key_size_for_algorithm(
|
||||||
|
cls,
|
||||||
|
key_type: psa_storage.Expr, bits: int,
|
||||||
|
alg: psa_storage.Expr
|
||||||
|
) -> bool:
|
||||||
|
"""Whether the given key type and size are valid for the algorithm.
|
||||||
|
|
||||||
|
Normally only the type and algorithm matter for compatibility, and
|
||||||
|
this is handled in crypto_knowledge.KeyType.can_do(). This function
|
||||||
|
exists to detect exceptional cases. Exceptional cases detected here
|
||||||
|
are not tested in OpFail and should therefore have manually written
|
||||||
|
test cases.
|
||||||
|
"""
|
||||||
|
#pylint: disable=unused-argument
|
||||||
|
# OAEP requires room for two hashes plus wrapping
|
||||||
|
m = cls.RSA_OAEP_RE.match(alg.string)
|
||||||
|
if m:
|
||||||
|
hash_alg = m.group(1)
|
||||||
|
hash_length = crypto_knowledge.Algorithm.hash_length(hash_alg)
|
||||||
|
key_length = (bits + 7) // 8
|
||||||
|
# Leave enough room for at least one byte of plaintext
|
||||||
|
return key_length > 2 * hash_length + 2
|
||||||
|
return True
|
||||||
|
|
||||||
def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
|
def make_test_case(self, key: StorageTestData) -> test_case.TestCase:
|
||||||
"""Construct a storage format test case for the given key.
|
"""Construct a storage format test case for the given key.
|
||||||
|
|
||||||
|
@ -546,7 +572,8 @@ class StorageFormat:
|
||||||
# encodings of the attributes.
|
# encodings of the attributes.
|
||||||
# Raw data keys have no useful exercise anyway so there is no
|
# Raw data keys have no useful exercise anyway so there is no
|
||||||
# loss of test coverage.
|
# loss of test coverage.
|
||||||
if key.type.string != 'PSA_KEY_TYPE_RAW_DATA':
|
if key.type.string != 'PSA_KEY_TYPE_RAW_DATA' and \
|
||||||
|
self.valid_key_size_for_algorithm(key.type, key.bits, key.alg):
|
||||||
flags.append('TEST_FLAG_EXERCISE')
|
flags.append('TEST_FLAG_EXERCISE')
|
||||||
if 'READ_ONLY' in key.lifetime.string:
|
if 'READ_ONLY' in key.lifetime.string:
|
||||||
flags.append('TEST_FLAG_READ_ONLY')
|
flags.append('TEST_FLAG_READ_ONLY')
|
||||||
|
|
Loading…
Reference in a new issue