diff --git a/cyclonedx/model/crypto.py b/cyclonedx/model/crypto.py index 44bdc182..eca00eee 100644 --- a/cyclonedx/model/crypto.py +++ b/cyclonedx/model/crypto.py @@ -344,8 +344,10 @@ class AlgorithmProperties: def __init__( self, *, primitive: Optional[CryptoPrimitive] = None, + algorithm_family: Optional[str] = None, parameter_set_identifier: Optional[str] = None, curve: Optional[str] = None, + elliptic_curve: Optional[str] = None, execution_environment: Optional[CryptoExecutionEnvironment] = None, implementation_platform: Optional[CryptoImplementationPlatform] = None, certification_levels: Optional[Iterable[CryptoCertificationLevel]] = None, @@ -356,8 +358,10 @@ def __init__( nist_quantum_security_level: Optional[int] = None, ) -> None: self.primitive = primitive + self.algorithm_family = algorithm_family self.parameter_set_identifier = parameter_set_identifier self.curve = curve + self.elliptic_curve = elliptic_curve self.execution_environment = execution_environment self.implementation_platform = implementation_platform self.certification_levels = certification_levels or [] @@ -392,7 +396,18 @@ def primitive(self, primitive: Optional[CryptoPrimitive]) -> None: self._primitive = primitive @property + @serializable.view(SchemaVersion1Dot7) @serializable.xml_sequence(2) + def algorithm_family(self) -> Optional[str]: + """The standardized family identifier for the algorithm.""" + return self._algorithm_family + + @algorithm_family.setter + def algorithm_family(self, algorithm_family: Optional[str]) -> None: + self._algorithm_family = algorithm_family + + @property + @serializable.xml_sequence(3) def parameter_set_identifier(self) -> Optional[str]: """ An identifier for the parameter set of the cryptographic algorithm. Examples: in AES128, '128' identifies the @@ -409,7 +424,7 @@ def parameter_set_identifier(self, parameter_set_identifier: Optional[str]) -> N self._parameter_set_identifier = parameter_set_identifier @property - @serializable.xml_sequence(3) + @serializable.xml_sequence(4) def curve(self) -> Optional[str]: """ The specific underlying Elliptic Curve (EC) definition employed which is an indicator of the level of security @@ -427,7 +442,18 @@ def curve(self, curve: Optional[str]) -> None: self._curve = curve @property - @serializable.xml_sequence(4) + @serializable.view(SchemaVersion1Dot7) + @serializable.xml_sequence(5) + def elliptic_curve(self) -> Optional[str]: + """The standardized identifier of the elliptic curve used by the algorithm.""" + return self._elliptic_curve + + @elliptic_curve.setter + def elliptic_curve(self, elliptic_curve: Optional[str]) -> None: + self._elliptic_curve = elliptic_curve + + @property + @serializable.xml_sequence(6) def execution_environment(self) -> Optional[CryptoExecutionEnvironment]: """ The target and execution environment in which the algorithm is implemented in. @@ -442,7 +468,7 @@ def execution_environment(self, execution_environment: Optional[CryptoExecutionE self._execution_environment = execution_environment @property - @serializable.xml_sequence(4) + @serializable.xml_sequence(7) def implementation_platform(self) -> Optional[CryptoImplementationPlatform]: """ The target platform for which the algorithm is implemented. The implementation can be 'generic', running on @@ -462,7 +488,7 @@ def implementation_platform(self, implementation_platform: Optional[CryptoImplem @serializable.view(SchemaVersion1Dot6) @serializable.view(SchemaVersion1Dot7) @serializable.xml_array(serializable.XmlArraySerializationType.FLAT, child_name='certificationLevel') - @serializable.xml_sequence(5) + @serializable.xml_sequence(8) def certification_levels(self) -> 'SortedSet[CryptoCertificationLevel]': """ The certification that the implementation of the cryptographic algorithm has received, if any. Certifications @@ -478,7 +504,7 @@ def certification_levels(self, certification_levels: Iterable[CryptoCertificatio self._certification_levels = SortedSet(certification_levels) @property - @serializable.xml_sequence(6) + @serializable.xml_sequence(9) def mode(self) -> Optional[CryptoMode]: """ The mode of operation in which the cryptographic algorithm (block cipher) is used. @@ -493,7 +519,7 @@ def mode(self, mode: Optional[CryptoMode]) -> None: self._mode = mode @property - @serializable.xml_sequence(8) + @serializable.xml_sequence(10) def padding(self) -> Optional[CryptoPadding]: """ The padding scheme that is used for the cryptographic algorithm. @@ -509,7 +535,7 @@ def padding(self, padding: Optional[CryptoPadding]) -> None: @property @serializable.xml_array(serializable.XmlArraySerializationType.NESTED, child_name='cryptoFunction') - @serializable.xml_sequence(9) + @serializable.xml_sequence(11) def crypto_functions(self) -> 'SortedSet[CryptoFunction]': """ The cryptographic functions implemented by the cryptographic algorithm. @@ -524,7 +550,7 @@ def crypto_functions(self, crypto_functions: Iterable[CryptoFunction]) -> None: self._crypto_functions = SortedSet(crypto_functions) @property - @serializable.xml_sequence(10) + @serializable.xml_sequence(12) def classical_security_level(self) -> Optional[int]: """ The classical security level that a cryptographic algorithm provides (in bits). @@ -539,7 +565,7 @@ def classical_security_level(self, classical_security_level: Optional[int]) -> N self._classical_security_level = classical_security_level @property - @serializable.xml_sequence(11) + @serializable.xml_sequence(13) def nist_quantum_security_level(self) -> Optional[int]: """ The NIST security strength category as defined in @@ -564,9 +590,10 @@ def nist_quantum_security_level(self, nist_quantum_security_level: Optional[int] def __comparable_tuple(self) -> _ComparableTuple: return _ComparableTuple(( - self.primitive, self._parameter_set_identifier, self.curve, self.execution_environment, - self.implementation_platform, _ComparableTuple(self.certification_levels), self.mode, self.padding, - _ComparableTuple(self.crypto_functions), self.classical_security_level, self.nist_quantum_security_level, + self.primitive, self.algorithm_family, self.parameter_set_identifier, self.curve, self.elliptic_curve, + self.execution_environment, self.implementation_platform, _ComparableTuple(self.certification_levels), + self.mode, self.padding, _ComparableTuple(self.crypto_functions), self.classical_security_level, + self.nist_quantum_security_level, )) def __eq__(self, other: object) -> bool: diff --git a/tests/_data/models.py b/tests/_data/models.py index e2052878..81d469c7 100644 --- a/tests/_data/models.py +++ b/tests/_data/models.py @@ -171,8 +171,10 @@ def get_crypto_properties_algorithm() -> CryptoProperties: asset_type=CryptoAssetType.ALGORITHM, algorithm_properties=AlgorithmProperties( primitive=CryptoPrimitive.KEM, + algorithm_family='ML-KEM', parameter_set_identifier='a-parameter-set-id', curve='9n8y2oxty3ao83n8qc2g2x3qcw4jt4wj', + elliptic_curve='nist/P-256', execution_environment=CryptoExecutionEnvironment.SOFTWARE_PLAIN_RAM, implementation_platform=CryptoImplementationPlatform.GENERIC, certification_levels=[ diff --git a/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.json.bin b/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.json.bin index 819b1821..bb9215d8 100644 --- a/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.json.bin +++ b/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.json.bin @@ -4,6 +4,7 @@ "bom-ref": "8182921e-0588-472e-b8f9-9c527c68f067", "cryptoProperties": { "algorithmProperties": { + "algorithmFamily": "ML-KEM", "certificationLevel": [ "fips140-1-l1", "fips140-2-l3", @@ -15,6 +16,7 @@ "unknown" ], "curve": "9n8y2oxty3ao83n8qc2g2x3qcw4jt4wj", + "ellipticCurve": "nist/P-256", "executionEnvironment": "software-plain-ram", "implementationPlatform": "generic", "mode": "ecb", diff --git a/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.xml.bin b/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.xml.bin index a31cf67d..73db0a46 100644 --- a/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.xml.bin +++ b/tests/_data/snapshots/get_bom_v1_6_with_crypto_algorithm-1.7.xml.bin @@ -11,8 +11,10 @@ algorithm kem + ML-KEM a-parameter-set-id 9n8y2oxty3ao83n8qc2g2x3qcw4jt4wj + nist/P-256 software-plain-ram generic fips140-1-l1 diff --git a/tests/test_model_crypto.py b/tests/test_model_crypto.py index 12265ee1..1ee394a1 100644 --- a/tests/test_model_crypto.py +++ b/tests/test_model_crypto.py @@ -15,6 +15,7 @@ # SPDX-License-Identifier: Apache-2.0 # Copyright (c) OWASP Foundation. All Rights Reserved. +from json import loads as json_loads from unittest import TestCase from cyclonedx.model.bom_ref import BomRef @@ -29,6 +30,7 @@ RelatedCryptoMaterialSecuredBy, RelatedCryptoMaterialType, ) +from cyclonedx.schema.schema import SchemaVersion1Dot6, SchemaVersion1Dot7 class TestModelAlgorithmProperties(TestCase): @@ -134,3 +136,22 @@ def test_protocol_properties_sorting(self) -> None: proto_list = [proto3, proto1, proto2] sorted_protos = sorted(proto_list) self.assertEqual(len(sorted_protos), 3) + + +class TestModelAlgorithmPropertiesV17(TestCase): + + def test_new_algorithm_identifiers_and_legacy_curve(self) -> None: + properties = AlgorithmProperties( + algorithm_family='AES', + curve='legacy-curve', + elliptic_curve='nist/P-256', + ) + data_1_7 = json_loads(properties.as_json(view_=SchemaVersion1Dot7)) + self.assertEqual('AES', data_1_7['algorithmFamily']) + self.assertEqual('nist/P-256', data_1_7['ellipticCurve']) + self.assertEqual('legacy-curve', data_1_7['curve']) + + data_1_6 = json_loads(properties.as_json(view_=SchemaVersion1Dot6)) + self.assertNotIn('algorithmFamily', data_1_6) + self.assertNotIn('ellipticCurve', data_1_6) + self.assertEqual('legacy-curve', data_1_6['curve'])