Skip to content

Commit 81f8cf5

Browse files
authored
feat: add function to map python hashlib algorithms to CycloneDX (#519)
new API: `model.HashType.from_hashlib_alg()` Signed-off-by: Jan Kowalleck <[email protected]>
1 parent 87c72d7 commit 81f8cf5

File tree

2 files changed

+72
-12
lines changed

2 files changed

+72
-12
lines changed

cyclonedx/model/__init__.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,25 @@ def xml_denormalize(cls, o: 'XmlElement', *,
343343
]
344344

345345

346+
_MAP_HASHLIB: Dict[str, HashAlgorithm] = {
347+
# from hashlib.algorithms_guaranteed
348+
'md5': HashAlgorithm.MD5,
349+
'sha1': HashAlgorithm.SHA_1,
350+
# sha224:
351+
'sha256': HashAlgorithm.SHA_256,
352+
'sha384': HashAlgorithm.SHA_384,
353+
'sha512': HashAlgorithm.SHA_512,
354+
# blake2b:
355+
# blake2s:
356+
# sha3_224:
357+
'sha3_256': HashAlgorithm.SHA3_256,
358+
'sha3_384': HashAlgorithm.SHA3_384,
359+
'sha3_512': HashAlgorithm.SHA3_512,
360+
# shake_128:
361+
# shake_256:
362+
}
363+
364+
346365
@serializable.serializable_class
347366
class HashType:
348367
"""
@@ -352,6 +371,30 @@ class HashType:
352371
See the CycloneDX Schema for hashType: https://cyclonedx.org/docs/1.3/#type_hashType
353372
"""
354373

374+
@staticmethod
375+
def from_hashlib_alg(hashlib_alg: str, content: str) -> 'HashType':
376+
"""
377+
Attempts to convert a hashlib-algorithm to our internal model classes.
378+
379+
Args:
380+
hashlib_alg:
381+
Hash algorith - like it is used by `hashlib`.
382+
Example: `sha256`.
383+
384+
content:
385+
Hash value.
386+
387+
Raises:
388+
`UnknownHashTypeException` if the algorithm of hash cannot be determined.
389+
390+
Returns:
391+
An instance of `HashType`.
392+
"""
393+
alg = _MAP_HASHLIB.get(hashlib_alg.lower())
394+
if alg is None:
395+
raise UnknownHashTypeException(f'Unable to determine hash alg for {hashlib_alg!r}')
396+
return HashType(alg=alg, content=content)
397+
355398
@staticmethod
356399
def from_composite_str(composite_hash: str) -> 'HashType':
357400
"""

tests/test_model.py

Lines changed: 29 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
from enum import Enum
2121
from unittest import TestCase
2222

23+
from ddt import ddt, named_data
24+
2325
from cyclonedx._internal.compare import ComparableTuple
2426
from cyclonedx.exception.model import (
2527
InvalidLocaleTypeException,
@@ -139,8 +141,8 @@ def test_compare_last_item_missing(self) -> None:
139141
self.assertNotEqual(tuple2, tuple1)
140142

141143
def test_compare_enum(self) -> None:
142-
tuple1 = ComparableTuple((DummyStringEnum.FIRST, ))
143-
tuple2 = ComparableTuple((DummyStringEnum.SECOND, ))
144+
tuple1 = ComparableTuple((DummyStringEnum.FIRST,))
145+
tuple2 = ComparableTuple((DummyStringEnum.SECOND,))
144146
self.assertLess(tuple1, tuple2)
145147
self.assertGreater(tuple2, tuple1)
146148
self.assertNotEqual(tuple1, tuple2)
@@ -239,19 +241,34 @@ def test_sort(self) -> None:
239241
self.assertListEqual(sorted_refs, expected_refs)
240242

241243

244+
@ddt
242245
class TestModelHashType(TestCase):
243246

244-
def test_hash_type_from_composite_str_1(self) -> None:
245-
h = HashType.from_composite_str('sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b')
246-
self.assertEqual(h.alg, HashAlgorithm.SHA_256)
247-
self.assertEqual(h.content, '806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b')
248-
249-
def test_hash_type_from_composite_str_2(self) -> None:
250-
h = HashType.from_composite_str('md5:dc26cd71b80d6757139f38156a43c545')
251-
self.assertEqual(h.alg, HashAlgorithm.MD5)
252-
self.assertEqual(h.content, 'dc26cd71b80d6757139f38156a43c545')
247+
@named_data(
248+
('sha256', 'sha256', '806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b', HashAlgorithm.SHA_256),
249+
('MD5', 'MD5', 'dc26cd71b80d6757139f38156a43c545', HashAlgorithm.MD5),
250+
)
251+
def test_hash_type_from_hashlib_alg(self, alg: str, content: str, e_alg: HashAlgorithm) -> None:
252+
h = HashType.from_hashlib_alg(alg, content)
253+
self.assertIs(h.alg, e_alg)
254+
self.assertEqual(h.content, content)
253255

254-
def test_hash_type_from_unknown(self) -> None:
256+
def test_hash_type_from_hashlib_alg_throws_on_unknown(self) -> None:
257+
with self.assertRaises(UnknownHashTypeException):
258+
HashType.from_hashlib_alg('unknown', 'dc26cd71b80d6757139f38156a43c545')
259+
260+
@named_data(
261+
('sha256', 'sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b',
262+
HashAlgorithm.SHA_256, '806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b'),
263+
('MD5', 'MD5:dc26cd71b80d6757139f38156a43c545',
264+
HashAlgorithm.MD5, 'dc26cd71b80d6757139f38156a43c545'),
265+
)
266+
def test_hash_type_from_composite_str(self, composite: str, e_alg: HashAlgorithm, e_content: str) -> None:
267+
h = HashType.from_composite_str(composite)
268+
self.assertIs(h.alg, e_alg)
269+
self.assertEqual(h.content, e_content)
270+
271+
def test_hash_type_from_composite_str_throws_on_unknown(self) -> None:
255272
with self.assertRaises(UnknownHashTypeException):
256273
HashType.from_composite_str('unknown:dc26cd71b80d6757139f38156a43c545')
257274

0 commit comments

Comments
 (0)