diff --git a/examples/write_tv.py b/examples/write_tv.py index 47c8cd494..3273b3b2d 100755 --- a/examples/write_tv.py +++ b/examples/write_tv.py @@ -15,7 +15,7 @@ from spdx.review import Review from spdx.package import Package from spdx.file import File, FileType - from spdx.checksum import Algorithm + from spdx.checksum import Checksum from spdx.utils import SPDXNone, NoAssert, UnKnown doc = Document() @@ -36,7 +36,7 @@ testfile1.type = FileType.BINARY testfile1.spdx_id = "TestFilet#SPDXRef-FILE" testfile1.comment = "This is a test file." - testfile1.chksum = Algorithm("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad") + testfile1.chksum = Checksum("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad") testfile1.conc_lics = License.from_identifier("BSD-2-Clause") testfile1.add_lics(License.from_identifier("BSD-2-Clause")) testfile1.copyright = SPDXNone() @@ -48,7 +48,7 @@ testfile2.type = FileType.SOURCE testfile2.spdx_id = "TestFile2#SPDXRef-FILE" testfile2.comment = "This is a test file." - testfile2.chksum = Algorithm("SHA1", "bb154f28d1cf0646ae21bb0bec6c669a2b90e113") + testfile2.chksum = Checksum("SHA1", "bb154f28d1cf0646ae21bb0bec6c669a2b90e113") testfile2.conc_lics = License.from_identifier("Apache-2.0") testfile2.add_lics(License.from_identifier("Apache-2.0")) testfile2.copyright = NoAssert() @@ -62,7 +62,7 @@ package.file_name = "twt.jar" package.spdx_id = 'TestPackage#SPDXRef-PACKAGE' package.download_location = "http://www.tagwritetest.test/download" - package.checksum = Algorithm("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad") + package.checksum = Checksum("SHA1", "c537c5d99eca5333f23491d47ededd083fefb7ad") package.homepage = SPDXNone() package.verif_code = "4e3211c67a2d28fced849ee1bb76e7391b93feba" license_set = LicenseConjunction( diff --git a/spdx/checksum.py b/spdx/checksum.py index d770f5a84..c17361818 100644 --- a/spdx/checksum.py +++ b/spdx/checksum.py @@ -8,14 +8,71 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. +import re +from enum import Enum, auto +from typing import Optional -class Algorithm(object): +class ChecksumAlgorithm(Enum): + SHA1 = auto() + SHA224 = auto() + SHA256 = auto() + SHA384 = auto() + SHA512 = auto() + SHA3_256 = auto() + SHA3_384 = auto() + SHA3_512 = auto() + BLAKE2B_256 = auto() + BLAKE2B_384 = auto() + BLAKE2B_512 = auto() + BLAKE3 = auto() + MD2 = auto() + MD4 = auto() + MD5 = auto() + MD6 = auto() + ADLER32 = auto() + + def algorithm_to_rdf_representation(self) -> str: + if self.name.startswith("BLAKE2B"): + return "checksumAlgorithm_" + self.name.replace("_", "").lower() + else: + return "checksumAlgorithm_" + self.name.lower() + + @classmethod + def checksum_from_rdf(cls, identifier: str) -> 'ChecksumAlgorithm': + identifier = identifier.split('_', 1)[-1].upper() + blake_checksum = re.compile(r"^(BLAKE2B)(256|384|512)$", re.UNICODE) + match = blake_checksum.match(identifier) + if match: + identifier = match[1] + '_' + match[2] + if identifier not in ChecksumAlgorithm.__members__: + raise ValueError(f"Invalid algorithm for checksum: {identifier}") + return ChecksumAlgorithm[identifier] + + @classmethod + def checksum_algorithm_from_string(cls, identifier: str) -> 'ChecksumAlgorithm': + identifier.replace("-", "_").upper() + if identifier not in ChecksumAlgorithm.__members__: + raise ValueError(f"Invalid algorithm for checksum: {identifier}") + return ChecksumAlgorithm[identifier] + + +class Checksum(object): """Generic checksum algorithm.""" - def __init__(self, identifier, value): + def __init__(self, identifier: ChecksumAlgorithm, value: str): self.identifier = identifier self.value = value + @classmethod + def checksum_from_string(cls, value: str) -> 'Checksum': + CHECKSUM_RE = re.compile("(ADLER32|BLAKE2b-256|BLAKE2b-384|BLAKE2b-512|BLAKE3|MD2|MD4|MD5|MD6|" \ + "SHA1|SHA224|SHA256|SHA384|SHA512|SHA3-256|SHA3-384|SHA3-512):\\s*([a-fA-F0-9]*)") + match = CHECKSUM_RE.match(value) + if match is None or match.group(1) is None or match.group(2) is None: + raise ValueError(f"Invalid checksum: {value}") + identifier = ChecksumAlgorithm.checksum_algorithm_from_string(match.group(1)) + return Checksum(identifier=identifier, value=match.group(2)) + def to_tv(self): - return "{0}: {1}".format(self.identifier, self.value) + return "{0}: {1}".format(self.identifier.name, self.value) diff --git a/spdx/cli_tools/parser.py b/spdx/cli_tools/parser.py index 97d3897e9..4b5d45a21 100755 --- a/spdx/cli_tools/parser.py +++ b/spdx/cli_tools/parser.py @@ -51,8 +51,8 @@ def main(file, force): "Package Download Location: {0}".format(package.download_location) ) print("Package Homepage: {0}".format(package.homepage)) - if package.checksum: - print("Package Checksum: {0}".format(package.checksum.value)) + for checksum in doc.package.checksums.values(): + print("Package Checksum: {0} {1}".format(checksum.identifier.name, checksum.value)) print("Package Attribution Text: {0}".format(package.attribution_text)) print("Package verification code: {0}".format(package.verif_code)) print( @@ -82,7 +82,8 @@ def main(file, force): print("\tFile name: {0}".format(f.name)) for file_type in f.file_types: print("\tFile type: {0}".format(file_type.name)) - print("\tFile Checksum: {0}".format(f.chksum.value)) + for file_checksum in f.checksums.values(): + print("\tFile Checksum: {0} {1}".format(file_checksum.identifier.name, file_checksum.value)) print("\tFile license concluded: {0}".format(f.conc_lics)) print( "\tFile license info in file: {0}".format( diff --git a/spdx/document.py b/spdx/document.py index 2e0a65950..c14e891d7 100644 --- a/spdx/document.py +++ b/spdx/document.py @@ -34,25 +34,25 @@ class ExternalDocumentRef(object): """ def __init__( - self, external_document_id=None, spdx_document_uri=None, check_sum=None + self, external_document_id=None, spdx_document_uri=None, checksum=None ): self.external_document_id = external_document_id self.spdx_document_uri = spdx_document_uri - self.check_sum = check_sum + self.checksum = checksum def __eq__(self, other): return ( isinstance(other, ExternalDocumentRef) and self.external_document_id == other.external_document_id and self.spdx_document_uri == other.spdx_document_uri - and self.check_sum == other.check_sum + and self.checksum == other.checksum ) def __lt__(self, other): - return (self.external_document_id, self.spdx_document_uri, self.check_sum) < ( + return (self.external_document_id, self.spdx_document_uri, self.checksum) < ( other.external_document_id, other.spdx_document_uri, - other.check_sum, + other.checksum, ) def validate(self, messages: ErrorMessages) -> ErrorMessages: @@ -74,7 +74,7 @@ def validate_spdx_doc_uri(self, messages): messages.append("ExternalDocumentRef has no SPDX Document URI.") def validate_checksum(self, messages): - if not self.check_sum: + if not self.checksum: messages.append("ExternalDocumentRef has no Checksum.") diff --git a/spdx/file.py b/spdx/file.py index eecf8b8f7..7781b7ea7 100644 --- a/spdx/file.py +++ b/spdx/file.py @@ -10,13 +10,15 @@ # limitations under the License. from enum import Enum, auto +import warnings from functools import total_ordering import hashlib -from spdx import checksum -from spdx import utils from spdx.license import License +from spdx import utils +from spdx.checksum import Checksum, ChecksumAlgorithm from spdx.parsers.builderexceptions import SPDXValueError +from spdx.parsers.loggers import ErrorMessages class FileType(Enum): @@ -58,7 +60,8 @@ class File(object): referenced by other elements. Mandatory, one. Type: str. - comment: File comment str, Optional zero or one. - file_types: list of file types. Cardinality 0..* - - chksum: SHA1, Mandatory one. + - checksums: Dict with checksum.ChecksumAlgorithm as key and checksum.Checksum as value, + there must be a SHA1 hash, at least. - conc_lics: Mandatory one. license.License or utils.NoAssert or utils.SPDXNone. - licenses_in_file: list of licenses found in file, mandatory one or more. document.License or utils.SPDXNone or utils.NoAssert. @@ -74,12 +77,12 @@ class File(object): -attribution_text: optional string. """ - def __init__(self, name, spdx_id=None, chksum=None): + def __init__(self, name, spdx_id=None): self.name = name self.spdx_id = spdx_id self.comment = None self.file_types = [] - self.checksums = [chksum] + self.checksums = {} self.conc_lics = None self.licenses_in_file = [] self.license_comment = None @@ -99,16 +102,23 @@ def __lt__(self, other): return self.name < other.name @property - def chksum(self): + def checksum(self): """ - Backwards compatibility, return first checksum. + Backwards compatibility, return SHA1 checksum. """ - # NOTE Package.check_sum but File.chk_sum - return self.checksums[0] + warnings.warn("This property is deprecated. Use get_checksum instead.") + return self.get_checksum(ChecksumAlgorithm.SHA1) - @chksum.setter - def chksum(self, value): - self.checksums[0] = value + @checksum.setter + def checksum(self, value): + """ + Backwards compatibility, set checksum. + """ + warnings.warn("This property is deprecated. Use set_checksum instead.") + if isinstance(value, str): + self.set_checksum(Checksum("SHA1", value)) + elif isinstance(value, Checksum): + self.set_checksum(value) def add_lics(self, lics): self.licenses_in_file.append(lics) @@ -135,7 +145,7 @@ def validate(self, messages): messages.push_context(self.name) self.validate_concluded_license(messages) self.validate_file_types(messages) - self.validate_checksum(messages) + self.validate_checksums(messages) self.validate_licenses_in_file(messages) self.validate_copyright(messages) self.validate_artifacts(messages) @@ -202,29 +212,37 @@ def validate_file_types(self, messages): messages.append(f"{file_type} is not of type FileType.") return messages - def validate_checksum(self, messages): - if not isinstance(self.chksum, checksum.Algorithm): - messages.append( - "File checksum must be instance of spdx.checksum.Algorithm" - ) - else: - if not self.chksum.identifier == "SHA1": - messages.append("File checksum algorithm must be SHA1") + def validate_checksums(self, messages: ErrorMessages): + for checksum in self.checksums.values(): + if not isinstance(checksum, Checksum): + messages.append("File checksum must be instance of spdx.checksum.Checksum.") - return messages + if self.get_checksum(ChecksumAlgorithm.SHA1) is None: + messages.append("At least one file checksum algorithm must be SHA1") - def calc_chksum(self): + def calculate_checksum(self, hash_algorithm='SHA1'): + if hash_algorithm not in ChecksumAlgorithm.__members__: + raise ValueError BUFFER_SIZE = 65536 - file_sha1 = hashlib.sha1() + file_hash = hashlib.new(hash_algorithm.lower()) with open(self.name, "rb") as file_handle: while True: data = file_handle.read(BUFFER_SIZE) if not data: break - file_sha1.update(data) + file_hash.update(data) + + return file_hash.hexdigest() + + def get_checksum(self, hash_algorithm: ChecksumAlgorithm = ChecksumAlgorithm.SHA1) -> Checksum: + return self.checksums[hash_algorithm] + + def set_checksum(self, new_checksum: Checksum): + if not isinstance(new_checksum, Checksum): + raise SPDXValueError - return file_sha1.hexdigest() + self.checksums[new_checksum.identifier] = new_checksum def has_optional_field(self, field): - return bool (getattr(self, field, None)) + return bool(getattr(self, field, None)) diff --git a/spdx/package.py b/spdx/package.py index aaa32f895..fdb909c36 100644 --- a/spdx/package.py +++ b/spdx/package.py @@ -8,16 +8,17 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. - +import warnings from datetime import datetime from enum import Enum from functools import reduce from typing import Optional -from spdx import checksum from spdx import creationinfo from spdx import license from spdx import utils +from spdx.checksum import Checksum, ChecksumAlgorithm +from spdx.parsers.builderexceptions import SPDXValueError from spdx.parsers.loggers import ErrorMessages @@ -59,7 +60,7 @@ class Package(object): whenever files_analyzed is True or None (omitted) and Must be None (omitted) if files_analyzed is False. However, as a convenience within this library, we allow this to be Optional even when files_analyzed is True/None. - - check_sum: Optional , spdx.checksum.Algorithm. + - checksums: Optional, Dict with checksum.ChecksumAlgorithm as key and checksum.Checksum as value. - source_info: Optional string. - conc_lics: Mandatory license.License or utils.SPDXNone or utils.NoAssert. @@ -100,7 +101,7 @@ def __init__( self.files_analyzed = None self.homepage = None self.verif_code = None - self.checksums = [None] + self.checksums = {} self.source_info = None self.conc_lics = None self.license_declared = None @@ -118,24 +119,30 @@ def __init__( self.built_date: Optional[datetime] = None self.valid_until_date: Optional[datetime] = None - - @property - def are_files_analyzed(self): - return self.files_analyzed is not False - # as default None Value is False, previous line is simplification of - # return self.files_analyzed or self.files_analyzed is None - @property def checksum(self): """ - Backwards compatibility, return first checksum. + Backwards compatibility, return SHA1 checksum. """ - # NOTE Package.check_sum but File.chk_sum - return self.checksums[0] + warnings.warn("This property is deprecated. Use get_checksum instead.") + return self.get_checksum(ChecksumAlgorithm.SHA1) @checksum.setter def checksum(self, value): - self.checksums[0] = value + """ + Backwards compatibility, set SHA1 checksum. + """ + warnings.warn("This property is deprecated. Use set_checksum instead.") + if isinstance(value, str): + self.set_checksum(Checksum("SHA1", value)) + elif isinstance(value, Checksum): + self.set_checksum(value) + + @property + def are_files_analyzed(self): + return self.files_analyzed is not False + # as default None Value is False, previous line is simplification of + # return self.files_analyzed or self.files_analyzed is None def add_lics_from_file(self, lics): self.licenses_from_files.append(lics) @@ -153,7 +160,7 @@ def validate(self, messages): """ messages.push_context(self.name) self.validate_files_analyzed(messages) - self.validate_checksum(messages) + self.validate_checksums(messages) self.validate_optional_str_fields(messages) self.validate_mandatory_str_fields(messages) self.validate_pkg_ext_refs(messages) @@ -284,18 +291,21 @@ def validate_str_fields(self, fields, optional, messages): return messages - def validate_checksum(self, messages): - if self.checksum is not None: - if not isinstance(self.checksum, checksum.Algorithm): - messages.append( - "Package checksum must be instance of spdx.checksum.Algorithm" - ) - elif not self.checksum.identifier == "SHA1": - messages.append( - "First checksum in package must be SHA1." - ) + def validate_checksums(self, messages: ErrorMessages): + if not self.checksums: + return + for checksum in self.checksums.values(): + if not isinstance(checksum, Checksum): + messages.append("Package checksum must be instance of spdx.checksum.Checksum") - return messages + def get_checksum(self, hash_algorithm: ChecksumAlgorithm = ChecksumAlgorithm.SHA1) -> Checksum: + return self.checksums[hash_algorithm] + + def set_checksum(self, new_checksum: Checksum): + if not isinstance(new_checksum, Checksum): + raise SPDXValueError("Package::Checksum") + + self.checksums[new_checksum.identifier] = new_checksum def has_optional_field(self, field): return bool(getattr(self, field, None)) diff --git a/spdx/parsers/jsonyamlxml.py b/spdx/parsers/jsonyamlxml.py index 2cdbbdc54..0e8916a6f 100644 --- a/spdx/parsers/jsonyamlxml.py +++ b/spdx/parsers/jsonyamlxml.py @@ -10,7 +10,7 @@ # limitations under the License. from datetime import datetime from enum import Enum, auto -from typing import List, Dict, Tuple, Callable +from typing import List, Dict, Tuple, Callable, Optional from spdx import document from spdx import utils @@ -823,7 +823,7 @@ def parse_file(self, file): self.parse_file_attribution_text(file.get("attributionTexts")) self.parse_file_dependencies(file.get("fileDependencies")) self.parse_annotations(file.get("annotations"), spdx_id=file.get("SPDXID")) - self.parse_file_chksum(file.get("sha1")) + self.parse_file_checksums(file.get("checksums")) else: self.value_error("FILE", file) @@ -1093,20 +1093,25 @@ def _handle_file_dependency(self, file_dependency): return None return None - def parse_file_chksum(self, file_chksum): + def parse_file_checksums(self, file_checksums: List[Dict]) -> Optional[bool]: """ - Parse File checksum - - file_chksum: Python str/unicode + Parse File checksums + - file_checksums: Python List """ - if isinstance(file_chksum, str): + if isinstance(file_checksums, list): + for checksum in file_checksums: + self.builder.set_file_checksum(self.document, checksum) + return True + if isinstance(file_checksums, str): + # kept for backwards compatibility try: - return self.builder.set_file_chksum(self.document, file_chksum) + return self.builder.set_file_checksum(self.document, file_checksums) except CardinalityError: self.more_than_one_error("FILE_CHECKSUM") except OrderError: self.order_error("FILE_CHECKSUM", "FILE_NAME") else: - self.value_error("FILE_CHECKSUM", file_chksum) + self.value_error("FILE_CHECKSUM", file_checksums) def parse_files(self, files: List[Dict]) -> None: if files is None: @@ -1158,7 +1163,7 @@ def parse_package(self, package: Package, method_to_parse_relationship: Callable self.parse_annotations(package.get("annotations"), spdx_id=package.get("SPDXID")) self.parse_pkg_attribution_text(package.get("attributionTexts")) self.parse_pkg_files(package.get("hasFiles"), method_to_parse_relationship) - self.parse_pkg_chksum(package.get("sha1")) + self.parse_pkg_checksums(package.get("checksums")) self.parse_package_external_refs(package.get("externalRefs")) self.parse_primary_package_purpose(package.get("primaryPackagePurpose")) self.parse_release_date(package.get("releaseDate")) @@ -1588,20 +1593,25 @@ def parse_pkg_files(self, pkg_has_files: List[str], method_to_parse_relationship elif pkg_has_files is not None: self.value_error("PKG_HAS_FILES", pkg_has_files) - def parse_pkg_chksum(self, pkg_chksum): + def parse_pkg_checksums(self, pkg_checksums: List[Dict]) -> Optional[bool]: """ - Parse Package checksum - - pkg_chksum: Python str/unicode + Parse Package checksums + - pkg_chksums: Python List """ - if isinstance(pkg_chksum, str): + if isinstance(pkg_checksums, list): + for checksum in pkg_checksums: + self.builder.set_pkg_checksum(self.document, checksum) + return True + if isinstance(pkg_checksums, str): + # kept for backwards compatibility try: - return self.builder.set_pkg_chk_sum(self.document, pkg_chksum) + return self.builder.set_pkg_checksum(self.document, pkg_checksums) except CardinalityError: self.more_than_one_error("PKG_CHECKSUM") except OrderError: self.order_error("PKG_CHECKSUM", "PKG_NAME") - elif pkg_chksum is not None: - self.value_error("PKG_CHECKSUM", pkg_chksum) + elif pkg_checksums is not None: + self.value_error("PKG_CHECKSUM", pkg_checksums) def parse_package_external_refs(self, external_refs: List[Dict]): if external_refs is None: diff --git a/spdx/parsers/jsonyamlxmlbuilders.py b/spdx/parsers/jsonyamlxmlbuilders.py index a0565b470..d514af7ab 100644 --- a/spdx/parsers/jsonyamlxmlbuilders.py +++ b/spdx/parsers/jsonyamlxmlbuilders.py @@ -8,10 +8,13 @@ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. -from spdx import file +from typing import Dict, Union + +from spdx.document import Document from spdx.parsers import rdfbuilders from spdx.parsers import tagvaluebuilders from spdx.parsers import validations +from spdx.checksum import Checksum, ChecksumAlgorithm from spdx.parsers.builderexceptions import SPDXValueError from spdx.parsers.builderexceptions import CardinalityError from spdx.parsers.builderexceptions import OrderError @@ -159,13 +162,32 @@ class FileBuilder(rdfbuilders.FileBuilder): def __init__(self): super(FileBuilder, self).__init__() + def set_file_checksum(self, doc: Document, checksum: Union[Dict, Checksum, str]) -> bool: + """ + Set the file checksum. + checksum - A string + raise OrderError if no file defined. + """ + if not self.has_file(doc): + raise OrderError("No file for checksum defined.") + + if isinstance(checksum, dict): + algo = checksum.get('algorithm') or 'SHA1' + identifier = ChecksumAlgorithm.checksum_algorithm_from_string(algo) + self.file(doc).set_checksum(Checksum(identifier, checksum.get('checksumValue'))) + elif isinstance(checksum, Checksum): + self.file(doc).set_checksum(checksum) + elif isinstance(checksum, str): + self.file(doc).set_checksum(Checksum(ChecksumAlgorithm.SHA1, checksum)) + return True + def set_file_notice(self, doc, text): """ Set file notice Raise OrderError if no package or file defined. Raise CardinalityError if more than one. """ - if self.has_package(doc) and self.has_file(doc): + if self.has_file(doc): self.file_notice_set = True self.file(doc).notice = text return True diff --git a/spdx/parsers/lexers/tagvalue.py b/spdx/parsers/lexers/tagvalue.py index ee27d5e7a..83c709a0d 100644 --- a/spdx/parsers/lexers/tagvalue.py +++ b/spdx/parsers/lexers/tagvalue.py @@ -151,7 +151,7 @@ def t_text(self, t): def t_text_end(self, t): r"\s*" t.type = "TEXT" - t.value = t.lexer.lexdata[t.lexer.text_start : t.lexer.lexpos] + t.value = t.lexer.lexdata[t.lexer.text_start: t.lexer.lexpos] t.lexer.lineno += t.value.count("\n") t.value = t.value.strip() t.lexer.begin("INITIAL") @@ -165,7 +165,8 @@ def t_text_error(self, t): print("Lexer error in text state") def t_CHKSUM(self, t): - r":\s*SHA1:\s*[a-f0-9]{40,40}" + r":\s*(ADLER32|BLAKE2b-256|BLAKE2b-384|BLAKE2b-512|BLAKE3|MD2|MD4|MD5|MD6|" \ + "SHA1|SHA224|SHA256|SHA384|SHA512|SHA3-256|SHA3-384|SHA3-512):\s*([a-fA-F0-9]*)" t.value = t.value[1:].strip() return t @@ -174,7 +175,6 @@ def t_RANGE(self, t): t.value = t.value[1:].strip() return t - def t_DOC_REF_ID(self, t): r":\s*DocumentRef-([A-Za-z0-9\+\.\-]+)" t.value = t.value[1:].strip() diff --git a/spdx/parsers/rdf.py b/spdx/parsers/rdf.py index a745a3697..279156867 100644 --- a/spdx/parsers/rdf.py +++ b/spdx/parsers/rdf.py @@ -21,6 +21,7 @@ from spdx import document from spdx import license from spdx import utils +from spdx.checksum import Checksum, ChecksumAlgorithm from spdx.parsers.builderexceptions import CardinalityError from spdx.parsers.builderexceptions import SPDXValueError from spdx.parsers.loggers import ErrorMessages @@ -66,6 +67,14 @@ } +def convert_rdf_checksum_algorithm(rdf_checksum_algorithm: str) -> ChecksumAlgorithm: + split_string = rdf_checksum_algorithm.split('#') + if len(split_string) != 2: + raise SPDXValueError('Unknown checksum algorithm {}'.format(rdf_checksum_algorithm)) + checksum_algorithm = ChecksumAlgorithm.checksum_from_rdf(split_string[1]) + return checksum_algorithm + + class BaseParser(object): """ Base class for all parsers. @@ -352,7 +361,7 @@ def parse_package(self, p_term): self.p_pkg_down_loc(p_term, self.spdx_namespace["downloadLocation"]) self.p_pkg_files_analyzed(p_term, self.spdx_namespace["filesAnalyzed"]) self.p_pkg_homepg(p_term, self.doap_namespace["homepage"]) - self.p_pkg_chk_sum(p_term, self.spdx_namespace["checksum"]) + self.p_pkg_checksum(p_term, self.spdx_namespace["checksum"]) self.p_pkg_src_info(p_term, self.spdx_namespace["sourceInfo"]) self.p_pkg_verif_code(p_term, self.spdx_namespace["packageVerificationCode"]) self.p_pkg_attribution_text(p_term, self.spdx_namespace["attributionText"]) @@ -503,16 +512,17 @@ def p_pkg_src_info(self, p_term, predicate): self.more_than_one_error("package source info") break - def p_pkg_chk_sum(self, p_term, predicate): - for _s, _p, checksum in self.graph.triples((p_term, predicate, None)): + def p_pkg_checksum(self, p_term, predicate): + for _s, _p, pkg_checksum in self.graph.triples((p_term, predicate, None)): for _, _, value in self.graph.triples( - (checksum, self.spdx_namespace["checksumValue"], None) + (pkg_checksum, self.spdx_namespace["checksumValue"], None) ): - try: - self.builder.set_pkg_chk_sum(self.doc, str(value)) - except CardinalityError: - self.more_than_one_error("Package checksum") - break + for _, _, algo in self.graph.triples( + (pkg_checksum, self.spdx_namespace["algorithm"], None) + ): + algorithm_identifier = convert_rdf_checksum_algorithm(str(algo)) + checksum = Checksum(algorithm_identifier, str(value)) + self.builder.set_pkg_checksum(self.doc, checksum) def p_pkg_homepg(self, p_term, predicate): for _s, _p, o in self.graph.triples((p_term, predicate, None)): @@ -615,7 +625,7 @@ def parse_file(self, f_term): self.p_file_spdx_id(f_term, self.spdx_namespace["File"]) self.p_file_type(f_term, self.spdx_namespace["fileType"]) - self.p_file_chk_sum(f_term, self.spdx_namespace["checksum"]) + self.p_file_checksum(f_term, self.spdx_namespace["checksum"]) self.p_file_lic_conc(f_term, self.spdx_namespace["licenseConcluded"]) self.p_file_lic_info(f_term, self.spdx_namespace["licenseInfoInFile"]) self.p_file_comments_on_lics(f_term, self.spdx_namespace["licenseComments"]) @@ -769,18 +779,20 @@ def p_file_type(self, f_term, predicate): except CardinalityError: self.more_than_one_error("file type") - def p_file_chk_sum(self, f_term, predicate): + def p_file_checksum(self, f_term, predicate): """ - Set file checksum. Assumes SHA1 algorithm without checking. + Set file checksum. """ - try: - for _s, _p, checksum in self.graph.triples((f_term, predicate, None)): - for _, _, value in self.graph.triples( - (checksum, self.spdx_namespace["checksumValue"], None) + for _s, _p, file_checksum in self.graph.triples((f_term, predicate, None)): + for _, _, value in self.graph.triples( + (file_checksum, self.spdx_namespace["checksumValue"], None) + ): + for _, _, algo in self.graph.triples( + (file_checksum, self.spdx_namespace["algorithm"], None) ): - self.builder.set_file_chksum(self.doc, str(value)) - except CardinalityError: - self.more_than_one_error("File checksum") + algorithm_identifier = convert_rdf_checksum_algorithm(str(algo)) + checksum = Checksum(algorithm_identifier, str(value)) + self.builder.set_file_checksum(self.doc, checksum) def p_file_lic_conc(self, f_term, predicate): """ diff --git a/spdx/parsers/rdfbuilders.py b/spdx/parsers/rdfbuilders.py index 78bf0f4ea..c7845ae4d 100644 --- a/spdx/parsers/rdfbuilders.py +++ b/spdx/parsers/rdfbuilders.py @@ -10,16 +10,20 @@ # limitations under the License. import re +from typing import Dict, Union -from spdx import checksum, file +from spdx import file from spdx import license from spdx import package from spdx import version +from spdx.checksum import Checksum, ChecksumAlgorithm +from spdx.document import Document from spdx.parsers.builderexceptions import CardinalityError from spdx.parsers.builderexceptions import OrderError from spdx.parsers.builderexceptions import SPDXValueError from spdx.parsers import tagvaluebuilders from spdx.parsers import validations +from spdx.parsers.rdf import convert_rdf_checksum_algorithm class DocBuilder(object): @@ -141,8 +145,8 @@ def set_chksum(self, doc, chk_sum): chk_sum - The checksum value in the form of a string. """ if chk_sum: - doc.ext_document_references[-1].check_sum = checksum.Algorithm( - "SHA1", chk_sum + doc.ext_document_references[-1].checksum = Checksum( + ChecksumAlgorithm.SHA1, chk_sum ) else: raise SPDXValueError("ExternalDocumentRef::Checksum") @@ -184,19 +188,28 @@ class PackageBuilder(tagvaluebuilders.PackageBuilder): def __init__(self): super(PackageBuilder, self).__init__() - def set_pkg_chk_sum(self, doc, chk_sum): + def set_pkg_checksum(self, doc, checksum: Union[Checksum, Dict]): """ - Set the package check sum, if not already set. - chk_sum - A string - Raise CardinalityError if already defined. + Set the package checksum. + checksum - A Checksum or a Dict + Raise SPDXValueError if checksum type invalid. Raise OrderError if no package previously defined. """ self.assert_package_exists() - if not self.package_chk_sum_set: - self.package_chk_sum_set = True - doc.packages[-1].checksum = checksum.Algorithm("SHA1", chk_sum) + if isinstance(checksum, dict): + algo = checksum.get('algorithm') or ChecksumAlgorithm.SHA1 + if algo.startswith('checksumAlgorithm_'): + algo = convert_rdf_checksum_algorithm(algo) or ChecksumAlgorithm.SHA1 + else: + algo = ChecksumAlgorithm.checksum_algorithm_from_string(algo) + doc.packages[-1].set_checksum(Checksum(identifier=algo, value=checksum.get('checksumValue'))) + elif isinstance(checksum, Checksum): + doc.packages[-1].set_checksum(checksum) + elif isinstance(checksum, str): + # kept for backwards compatibility + doc.packages[-1].set_checksum(Checksum(identifier=ChecksumAlgorithm.SHA1, value=checksum)) else: - raise CardinalityError("Package::CheckSum") + raise SPDXValueError("Invalid value for package checksum.") def set_pkg_source_info(self, doc, text): """ @@ -380,22 +393,22 @@ class FileBuilder(tagvaluebuilders.FileBuilder): def __init__(self): super(FileBuilder, self).__init__() - def set_file_chksum(self, doc, chk_sum): + def set_file_checksum(self, doc: Document, chk_sum: Union[Checksum, Dict, str]): """ Set the file check sum, if not already set. - chk_sum - A string - Raise CardinalityError if already defined. - Raise OrderError if no package previously defined. - """ - if self.has_package(doc) and self.has_file(doc): - if not self.file_chksum_set: - self.file_chksum_set = True - self.file(doc).chksum = checksum.Algorithm("SHA1", chk_sum) - return True - else: - raise CardinalityError("File::CheckSum") - else: - raise OrderError("File::CheckSum") + chk_sum - A checksum.Checksum or a dict + """ + if self.has_file(doc): + if isinstance(chk_sum, dict): + identifier = ChecksumAlgorithm.checksum_algorithm_from_string(chk_sum.get('algorithm')) + self.file(doc).set_checksum(Checksum(identifier, + chk_sum.get('checksumValue'))) + elif isinstance(chk_sum, Checksum): + self.file(doc).set_checksum(chk_sum) + elif isinstance(chk_sum, str): + # kept for backwards compatibility + self.file(doc).set_checksum(Checksum(ChecksumAlgorithm.SHA1, chk_sum)) + return True def set_file_license_comment(self, doc, text): """ diff --git a/spdx/parsers/tagvalue.py b/spdx/parsers/tagvalue.py index 9444db1e6..ec83e92c7 100644 --- a/spdx/parsers/tagvalue.py +++ b/spdx/parsers/tagvalue.py @@ -650,7 +650,7 @@ def p_file_chksum_1(self, p): """file_chksum : FILE_CHKSUM CHKSUM""" try: value = p[2] - self.builder.set_file_chksum(self.document, value) + self.builder.set_file_checksum(self.document, value) except OrderError: self.order_error("FileChecksum", "FileName", p.lineno(1)) except CardinalityError: @@ -934,7 +934,7 @@ def p_pkg_chksum_1(self, p): """pkg_chksum : PKG_CHKSUM CHKSUM""" try: value = p[2] - self.builder.set_pkg_chk_sum(self.document, value) + self.builder.set_pkg_checksum(self.document, value) except OrderError: self.order_error("PackageChecksum", "PackageFileName", p.lineno(1)) except CardinalityError: diff --git a/spdx/parsers/tagvaluebuilders.py b/spdx/parsers/tagvaluebuilders.py index 33bfdad83..5f03816c4 100644 --- a/spdx/parsers/tagvaluebuilders.py +++ b/spdx/parsers/tagvaluebuilders.py @@ -9,12 +9,10 @@ # See the License for the specific language governing permissions and # limitations under the License. -from enum import auto, Enum import re from typing import Dict from spdx import annotation -from spdx import checksum from spdx import creationinfo from spdx import file from spdx import license @@ -24,8 +22,9 @@ from spdx import snippet from spdx import utils from spdx import version +from spdx.checksum import Checksum -from spdx.document import ExternalDocumentRef +from spdx.document import ExternalDocumentRef, Document from spdx.package import PackagePurpose from spdx.parsers.builderexceptions import CardinalityError from spdx.parsers.builderexceptions import OrderError @@ -33,20 +32,6 @@ from spdx.parsers import validations -def checksum_from_sha1(value): - """ - Return an spdx.checksum.Algorithm instance representing the SHA1 - checksum or None if does not match CHECKSUM_RE. - """ - # More constrained regex at lexer level - CHECKSUM_RE = re.compile("SHA1:\\s*([\\S]+)", re.UNICODE) - match = CHECKSUM_RE.match(value) - if match: - return checksum.Algorithm(identifier="SHA1", value=match.group(1)) - else: - return None - - def str_from_text(text) -> str: """ Return content of a free form text block as a string. @@ -202,7 +187,7 @@ def set_chksum(self, doc, chksum): """ Set the `check_sum` attribute of the `ExternalDocumentRef` object. """ - doc.ext_document_references[-1].check_sum = checksum_from_sha1(chksum) + doc.ext_document_references[-1].checksum = Checksum.checksum_from_string(chksum) def add_ext_doc_refs(self, doc, ext_doc_id, spdx_doc_uri, chksum): self.set_ext_doc_id(doc, ext_doc_id) @@ -780,19 +765,15 @@ def set_pkg_verif_code(self, doc, code): ).split(",") return True - def set_pkg_chk_sum(self, doc, chk_sum): + def set_pkg_checksum(self, doc, checksum): """ - Set the package check sum, if not already set. - chk_sum - A string - Raise CardinalityError if already defined. + Set the package checksum, if not already set. + checksum - A string Raise OrderError if no package previously defined. """ self.assert_package_exists() - if self.package_chk_sum_set: - raise CardinalityError("Package::CheckSum") - self.package_chk_sum_set = True - doc.packages[-1].checksum = checksum_from_sha1(chk_sum) + doc.packages[-1].set_checksum(Checksum.checksum_from_string(checksum)) return True def set_pkg_source_info(self, doc, text): @@ -1197,19 +1178,15 @@ def set_file_type(self, doc, type_value): spdx_file.file_types.append(file_type) - def set_file_chksum(self, doc, chksum): + def set_file_checksum(self, doc: Document, checksum: str): """ - Raise OrderError if no package or file defined. - Raise CardinalityError if more than one chksum set. + Raise OrderError if no file defined. """ - if not self.has_file(doc): + if self.has_file(doc): + new_checksum = Checksum.checksum_from_string(checksum) + self.file(doc).set_checksum(new_checksum) + else: raise OrderError("File::CheckSum") - - if self.file_chksum_set: - raise CardinalityError("File::CheckSum") - - self.file_chksum_set = True - self.file(doc).chksum = checksum_from_sha1(chksum) return True def set_concluded_license(self, doc, lic): diff --git a/spdx/utils.py b/spdx/utils.py index 115ea3ae1..3695cd0bd 100644 --- a/spdx/utils.py +++ b/spdx/utils.py @@ -17,10 +17,11 @@ from ply import lex from ply import yacc -from spdx import checksum +from spdx.checksum import ChecksumAlgorithm + if TYPE_CHECKING: from spdx.file import File -from spdx.package import Package + from spdx.package import Package from spdx.relationship import Relationship from spdx import license @@ -211,26 +212,24 @@ def parse(self, data): def calc_verif_code(files: List['File']) -> str: - hashes = [] - - for file_entry in files: - if ( - isinstance(file_entry.chksum, checksum.Algorithm) - and file_entry.chksum.identifier == "SHA1" - ): - sha1 = file_entry.chksum.value + list_of_file_hashes = [] + hash_algorithm_name = ChecksumAlgorithm.SHA1 + for file in files: + file_checksum = file.get_checksum(hash_algorithm_name) + if file_checksum is not None: + file_checksum_value = file_checksum.value else: - sha1 = file_entry.calc_chksum() - hashes.append(sha1) + file_checksum_value = file.calculate_checksum(hash_algorithm_name) + list_of_file_hashes.append(file_checksum_value) - hashes.sort() + list_of_file_hashes.sort() - sha1 = hashlib.sha1() - sha1.update("".join(hashes).encode("utf-8")) - return sha1.hexdigest() + hasher = hashlib.new(hash_algorithm_name.name.lower()) + hasher.update("".join(list_of_file_hashes).encode("utf-8")) + return hasher.hexdigest() -def get_files_in_package(package: Package, files: List['File'], relationships: List[Relationship]) -> List['File']: +def get_files_in_package(package: 'Package', files: List['File'], relationships: List[Relationship]) -> List['File']: files_in_package = [] for file in files: if file.spdx_id in [relationship.related_spdx_element for relationship in relationships @@ -247,33 +246,3 @@ def update_dict_item_with_new_item(current_state: Dict, key: str, item_to_add: s current_state[key] = [item_to_add] elif item_to_add not in current_state[key]: current_state[key].append(item_to_add) - - -def calc_verif_code(files: List['File']) -> str: - hashes = [] - - for file_entry in files: - if ( - isinstance(file_entry.chksum, checksum.Algorithm) - and file_entry.chksum.identifier == "SHA1" - ): - sha1 = file_entry.chksum.value - else: - sha1 = file_entry.calc_chksum() - hashes.append(sha1) - - hashes.sort() - - sha1 = hashlib.sha1() - sha1.update("".join(hashes).encode("utf-8")) - return sha1.hexdigest() - - -def get_files_in_package(package: Package, files: List['File'], relationships: List[Relationship]) -> List['File']: - files_in_package = [] - for file in files: - if file.spdx_id in [relationship.related_spdx_element for relationship in relationships - if relationship.relationship_type == "CONTAINS" and relationship.spdx_element_id == package.spdx_id]: - files_in_package.append(file) - - return files_in_package diff --git a/spdx/writers/jsonyamlxml.py b/spdx/writers/jsonyamlxml.py index 9a612ee1d..3f0f8bac8 100644 --- a/spdx/writers/jsonyamlxml.py +++ b/spdx/writers/jsonyamlxml.py @@ -13,6 +13,7 @@ from rdflib import Literal from spdx import license, utils +from spdx.checksum import Checksum from spdx.package import ExternalPackageRef from spdx.relationship import Relationship from spdx.utils import update_dict_item_with_new_item @@ -45,16 +46,11 @@ def license(self, license_field): license_str = license_field.__str__() return license_str - def checksum(self, checksum_field): + def checksum_to_dict(self, checksum_field: Checksum) -> Dict: """ - Return a dictionary representation of a spdx.checksum.Algorithm object + Return a dictionary representation of a checksum.Checksum object """ - checksum_object = dict() - checksum_object["algorithm"] = ( - checksum_field.identifier.upper() - ) - checksum_object["checksumValue"] = checksum_field.value - return checksum_object + return {'algorithm': checksum_field.identifier.name, 'checksumValue': checksum_field.value} def spdx_id(self, spdx_id_field): return spdx_id_field.__str__().split("#")[-1] @@ -171,8 +167,8 @@ def create_package_info(self, package, annotations_by_spdx_id): if package.has_optional_field("originator"): package_object["originator"] = package.originator.to_value() - if package.has_optional_field("checksum"): - package_object["checksums"] = [self.checksum(checksum) for checksum in package.checksums if checksum] + for checksum in package.checksums.values(): + package_object.setdefault("checksums", []).append(self.checksum_to_dict(checksum)) if package.has_optional_field("description"): package_object["description"] = package.description @@ -235,8 +231,8 @@ def create_file_info(self, file, annotations_by_spdx_id): file_object["fileName"] = file.name file_object["SPDXID"] = self.spdx_id(file.spdx_id) - file_object["checksums"] = [self.checksum(file.chksum)] - + for checksum in file.checksums.values(): + file_object.setdefault("checksums", []).append(self.checksum_to_dict(checksum)) if file.has_optional_field("conc_lics"): file_object["licenseConcluded"] = self.license(file.conc_lics) @@ -522,8 +518,8 @@ def create_ext_document_references(self): "spdxDocument" ] = ext_document_reference.spdx_document_uri - ext_document_reference_object["checksum"] = self.checksum( - ext_document_reference.check_sum + ext_document_reference_object["checksum"] = self.checksum_to_dict( + ext_document_reference.checksum ) ext_document_reference_objects.append(ext_document_reference_object) diff --git a/spdx/writers/rdf.py b/spdx/writers/rdf.py index 6d99dcb80..193c46658 100644 --- a/spdx/writers/rdf.py +++ b/spdx/writers/rdf.py @@ -22,6 +22,7 @@ from spdx import file from spdx import license from spdx import utils +from spdx.checksum import Checksum from spdx.package import Package from spdx.parsers.loggers import ErrorMessages from spdx.relationship import Relationship @@ -44,26 +45,27 @@ def __init__(self, document, out): self.spdx_namespace = Namespace("http://spdx.org/rdf/terms#") self.graph = Graph() - def create_checksum_node(self, chksum): + def create_checksum_node(self, checksum: Checksum) -> BNode: """ Return a node representing spdx.checksum. """ - chksum_node = BNode() - type_triple = (chksum_node, RDF.type, self.spdx_namespace.Checksum) + algo = checksum.identifier.algorithm_to_rdf_representation() or 'checksumAlgorithm_sha1' + checksum_node = BNode() + type_triple = (checksum_node, RDF.type, self.spdx_namespace.Checksum) self.graph.add(type_triple) algorithm_triple = ( - chksum_node, + checksum_node, self.spdx_namespace.algorithm, - Literal(chksum.identifier), + Literal('http://spdx.org/rdf/terms#' + algo), ) self.graph.add(algorithm_triple) value_triple = ( - chksum_node, + checksum_node, self.spdx_namespace.checksumValue, - Literal(chksum.value), + Literal(checksum.value), ) self.graph.add(value_triple) - return chksum_node + return checksum_node def to_special_value(self, value): """ @@ -264,13 +266,14 @@ def create_file_node(self, doc_file): ftype_triple = (file_node, self.spdx_namespace.fileType, ftype) self.graph.add(ftype_triple) - self.graph.add( - ( - file_node, - self.spdx_namespace.checksum, - self.create_checksum_node(doc_file.chksum), + for chk_sum in doc_file.checksums.values(): + self.graph.add( + ( + file_node, + self.spdx_namespace.checksum, + self.create_checksum_node(chk_sum), + ) ) - ) conc_lic_node = self.license_or_special(doc_file.conc_lics) conc_lic_triple = ( @@ -665,7 +668,7 @@ def create_external_document_ref_node(self, ext_document_references): doc_uri_triple = (ext_doc_ref_node, self.spdx_namespace.spdxDocument, doc_uri) self.graph.add(doc_uri_triple) - checksum_node = self.create_checksum_node(ext_document_references.check_sum) + checksum_node = self.create_checksum_node(ext_document_references.checksum) self.graph.add((ext_doc_ref_node, self.spdx_namespace.checksum, checksum_node)) return ext_doc_ref_node @@ -770,9 +773,10 @@ def handle_pkg_optional_fields(self, package: Package, package_node): package, package_node, self.spdx_namespace.filesAnalyzed, "files_analyzed" ) - if package.has_optional_field("checksum"): - checksum_node = self.create_checksum_node(package.checksum) - self.graph.add((package_node, self.spdx_namespace.checksum, checksum_node)) + if package.has_optional_field("checksums"): + for checksum in package.checksums.values(): + checksum_node = self.create_checksum_node(checksum) + self.graph.add((package_node, self.spdx_namespace.checksum, checksum_node)) if package.has_optional_field("homepage"): homepage_node = URIRef(self.to_special_value(package.homepage)) diff --git a/spdx/writers/tagvalue.py b/spdx/writers/tagvalue.py index d60c01bdc..f3abbbd39 100644 --- a/spdx/writers/tagvalue.py +++ b/spdx/writers/tagvalue.py @@ -124,7 +124,8 @@ def write_file(spdx_file, out): write_value("SPDXID", spdx_file.spdx_id, out) for file_type in spdx_file.file_types: write_file_type(file_type.name, out) - write_value("FileChecksum", spdx_file.chksum.to_tv(), out) + for file_checksum in spdx_file.checksums.values(): + write_value("FileChecksum", file_checksum.to_tv(), out) if spdx_file.has_optional_field("conc_lics"): if isinstance( spdx_file.conc_lics, (license.LicenseConjunction, license.LicenseDisjunction) @@ -239,8 +240,8 @@ def write_package(package, out): if package.has_optional_field("originator"): write_value("PackageOriginator", package.originator, out) - if package.has_optional_field("checksum"): - write_value("PackageChecksum", package.checksum.to_tv(), out) + for package_checksum in package.checksums.values(): + write_value("PackageChecksum", package_checksum.to_tv(), out) if package.has_optional_field("verif_code"): write_value("PackageVerificationCode", format_verif_code(package), out) @@ -356,7 +357,7 @@ def write_document(document, out, validate=True): [ doc_ref.external_document_id, doc_ref.spdx_document_uri, - doc_ref.check_sum.identifier + ":" + doc_ref.check_sum.value, + doc_ref.checksum.identifier.name + ": " + doc_ref.checksum.value, ] ) write_value("ExternalDocumentRef", doc_ref_str, out) diff --git a/tests/data/doc_parse/SBOMexpected.json b/tests/data/doc_parse/SBOMexpected.json index 6fdfbe7d0..3d0504388 100644 --- a/tests/data/doc_parse/SBOMexpected.json +++ b/tests/data/doc_parse/SBOMexpected.json @@ -64,7 +64,10 @@ }, "copyrightText": "copyright 2004-2020 Example Inc. All Rights Reserved.", "licenseComment": null, - "checksum": null, + "checksums": [{ + "identifier": "SHA1", + "value": "SOME-SHA1" + }], "licenseInfoFromFiles": [], "verificationCode": { "value": null, @@ -95,7 +98,10 @@ }, "copyrightText": "Copyright (c) 1996 - 2020, Daniel Stenberg, , and many contributors, see the THANKS file.", "licenseComment": null, - "checksum": null, + "checksums": [{ + "identifier": "SHA1", + "value": "SOME-SHA1" + }], "licenseInfoFromFiles": [], "verificationCode": { "value": null, @@ -126,7 +132,10 @@ }, "copyrightText": "copyright 2004-2020 The OpenSSL Project Authors. All Rights Reserved.", "licenseComment": null, - "checksum": null, + "checksums": [{ + "identifier": "SHA1", + "value": "SOME-SHA1" + }], "licenseInfoFromFiles": [], "verificationCode": { "value": null, diff --git a/tests/data/doc_parse/expected.json b/tests/data/doc_parse/expected.json index a7466e1a2..84f6689d1 100644 --- a/tests/data/doc_parse/expected.json +++ b/tests/data/doc_parse/expected.json @@ -48,10 +48,13 @@ "copyrightText": "(c) Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP", "licenseComment": "This license is used by Jena", "notice": null, - "checksum": { - "identifier": "SHA1", - "value": "3ab4e1c67a2d28fced849ee1bb76e7391b93f125" - }, + "checksums": [{ + "identifier": "SHA1", + "value": "3ab4e1c67a2d28fced849ee1bb76e7391b93f125" + },{ + "identifier": "SHA256", + "value": "3ab4e1c67a2d28fced849ee1bb76e7391b93f1250000000000000000" + }], "licenseInfoInFiles": [ { "type": "Single", @@ -84,10 +87,10 @@ "copyrightText": "Copyright 2010, 2011 Source Auditor Inc.", "licenseComment": null, "notice": null, - "checksum": { + "checksums": [{ "identifier": "SHA1", "value": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" - }, + }], "licenseInfoInFiles": [ { "type": "Single", @@ -165,10 +168,10 @@ }, "copyrightText": " Copyright 2010, 2011 Source Auditor Inc.", "licenseComment": "The declared license information can be found in the NOTICE file at the root of the archive file", - "checksum": { + "checksums": [{ "identifier": "SHA1", "value": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" - }, + }], "licenseInfoFromFiles": [ { "type": "Single", diff --git a/tests/data/doc_parse/spdx-expected.json b/tests/data/doc_parse/spdx-expected.json index 154840727..fce55cb2c 100644 --- a/tests/data/doc_parse/spdx-expected.json +++ b/tests/data/doc_parse/spdx-expected.json @@ -48,10 +48,16 @@ "copyrightText": "(c) Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP", "licenseComment": "This license is used by Jena", "notice": null, - "checksum": { - "identifier": "SHA1", - "value": "3ab4e1c67a2d28fced849ee1bb76e7391b93f125" - }, + "checksums": [ + { + "identifier": "SHA1", + "value": "3ab4e1c67a2d28fced849ee1bb76e7391b93f125" + }, + { + "identifier": "SHA256", + "value": "3ab4e1c67a2d28fced849ee1bb76e7391b93f1250000000000000000" + } + ], "licenseInfoInFiles": [ { "type": "Single", @@ -82,10 +88,16 @@ "copyrightText": "Copyright 2010, 2011 Source Auditor Inc.", "licenseComment": null, "notice": null, - "checksum": { - "identifier": "SHA1", - "value": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" - }, + "checksums": [ + { + "identifier": "SHA1", + "value": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" + }, + { + "identifier": "SHA256", + "value": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb120000000000000000" + } + ], "licenseInfoInFiles": [ { "type": "Single", @@ -163,10 +175,12 @@ }, "copyrightText": " Copyright 2010, 2011 Source Auditor Inc.", "licenseComment": "The declared license information can be found in the NOTICE file at the root of the archive file", - "checksum": { - "identifier": "SHA1", - "value": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" - }, + "checksums": [ + { + "identifier": "SHA1", + "value": "2fd4e1c67a2d28fced849ee1bb76e7391b93eb12" + } + ], "licenseInfoFromFiles": [ { "type": "Single", diff --git a/tests/data/doc_write/json-simple-plus.json b/tests/data/doc_write/json-simple-plus.json index d414159db..e9be761eb 100644 --- a/tests/data/doc_write/json-simple-plus.json +++ b/tests/data/doc_write/json-simple-plus.json @@ -27,6 +27,10 @@ { "algorithm": "SHA1", "checksumValue": "SOME-SHA1" + }, + { + "algorithm": "SHA256", + "checksumValue": "SOME-SHA256" } ], "licenseDeclared": "NOASSERTION", @@ -37,10 +41,10 @@ "hasFiles": [ "SPDXRef-File" ], - "primaryPackagePurpose" : "FILE", - "releaseDate" : "2021-01-01T12:00:00Z", - "builtDate" : "2021-01-01T12:00:00Z", - "validUntilDate" : "2022-01-01T12:00:00Z" + "primaryPackagePurpose": "FILE", + "releaseDate": "2021-01-01T12:00:00Z", + "builtDate": "2021-01-01T12:00:00Z", + "validUntilDate": "2022-01-01T12:00:00Z" } ], "files": [ @@ -50,6 +54,10 @@ { "algorithm": "SHA1", "checksumValue": "SOME-SHA1" + }, + { + "algorithm": "SHA256", + "checksumValue": "SOME-SHA256" } ], "licenseConcluded": "NOASSERTION", diff --git a/tests/data/doc_write/json-simple.json b/tests/data/doc_write/json-simple.json index 760ef5e3c..30c1b8943 100644 --- a/tests/data/doc_write/json-simple.json +++ b/tests/data/doc_write/json-simple.json @@ -27,6 +27,10 @@ { "algorithm": "SHA1", "checksumValue": "SOME-SHA1" + }, + { + "algorithm": "SHA256", + "checksumValue": "SOME-SHA256" } ], "licenseDeclared": "NOASSERTION", @@ -37,10 +41,10 @@ "hasFiles": [ "SPDXRef-File" ], - "primaryPackagePurpose" : "FILE", - "releaseDate" : "2021-01-01T12:00:00Z", - "builtDate" : "2021-01-01T12:00:00Z", - "validUntilDate" : "2022-01-01T12:00:00Z" + "primaryPackagePurpose": "FILE", + "releaseDate": "2021-01-01T12:00:00Z", + "builtDate": "2021-01-01T12:00:00Z", + "validUntilDate": "2022-01-01T12:00:00Z" } ], "files": [ @@ -50,6 +54,10 @@ { "algorithm": "SHA1", "checksumValue": "SOME-SHA1" + }, + { + "algorithm": "SHA256", + "checksumValue": "SOME-SHA256" } ], "licenseConcluded": "NOASSERTION", diff --git a/tests/data/doc_write/rdf-simple-plus.json b/tests/data/doc_write/rdf-simple-plus.json index 022479e80..d2b65aeed 100644 --- a/tests/data/doc_write/rdf-simple-plus.json +++ b/tests/data/doc_write/rdf-simple-plus.json @@ -26,12 +26,20 @@ "@rdf:resource": "http://spdx.org/rdf/terms#noassertion" }, "ns1:copyrightText": "Some copyright", - "ns1:checksum": { - "ns1:Checksum": { - "ns1:algorithm": "SHA1", - "ns1:checksumValue": "SOME-SHA1" - } - } + "ns1:checksum": [ + { + "ns1:Checksum": { + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha1", + "ns1:checksumValue": "SOME-SHA1" + } + }, + { + "ns1:Checksum": { + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha256", + "ns1:checksumValue": "SOME-SHA256" + } + } + ] } }, "ns1:specVersion": "SPDX-2.1", @@ -45,12 +53,19 @@ "ns1:File": { "@rdf:about": "http://www.spdx.org/files#SPDXRef-File", "ns1:fileName": "./some/path/tofile", - "ns1:checksum": { - "ns1:Checksum": { - "ns1:checksumValue": "SOME-SHA1", - "ns1:algorithm": "SHA1" + "ns1:checksum": [ + { + "ns1:Checksum": { + "ns1:checksumValue": "SOME-SHA1", + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha1" + } + }, { + "ns1:Checksum": { + "ns1:checksumValue": "SOME-SHA256", + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha256" + } } - }, + ], "ns1:licenseConcluded": { "@rdf:resource": "http://spdx.org/rdf/terms#noassertion" }, diff --git a/tests/data/doc_write/rdf-simple.json b/tests/data/doc_write/rdf-simple.json index 21004560c..00064a345 100644 --- a/tests/data/doc_write/rdf-simple.json +++ b/tests/data/doc_write/rdf-simple.json @@ -26,12 +26,20 @@ "@rdf:resource": "http://spdx.org/rdf/terms#noassertion" }, "ns1:copyrightText": "Some copyright", - "ns1:checksum": { - "ns1:Checksum": { - "ns1:algorithm": "SHA1", - "ns1:checksumValue": "SOME-SHA1" - } - } + "ns1:checksum": [ + { + "ns1:Checksum": { + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha1", + "ns1:checksumValue": "SOME-SHA1" + } + }, + { + "ns1:Checksum": { + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha256", + "ns1:checksumValue": "SOME-SHA256" + } + } + ] } }, "ns1:specVersion": "SPDX-2.1", @@ -47,12 +55,19 @@ "ns1:licenseInfoInFile": { "@rdf:resource": "http://spdx.org/licenses/LGPL-2.1-only" }, - "ns1:checksum": { - "ns1:Checksum": { - "ns1:checksumValue": "SOME-SHA1", - "ns1:algorithm": "SHA1" + "ns1:checksum": [ + { + "ns1:Checksum": { + "ns1:checksumValue": "SOME-SHA1", + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha1" + } + }, { + "ns1:Checksum": { + "ns1:checksumValue": "SOME-SHA256", + "ns1:algorithm": "http://spdx.org/rdf/terms#checksumAlgorithm_sha256" } - }, + } + ], "ns1:licenseConcluded": { "@rdf:resource": "http://spdx.org/rdf/terms#noassertion" }, diff --git a/tests/data/doc_write/tv-simple-plus.tv b/tests/data/doc_write/tv-simple-plus.tv index 5258e8a98..19b458f6c 100644 --- a/tests/data/doc_write/tv-simple-plus.tv +++ b/tests/data/doc_write/tv-simple-plus.tv @@ -12,6 +12,7 @@ PackageName: some/path SPDXID: SPDXRef-Package PackageDownloadLocation: NOASSERTION PackageChecksum: SHA1: SOME-SHA1 +PackageChecksum: SHA256: SOME-SHA256 PackageVerificationCode: SOME code PackageLicenseDeclared: NOASSERTION PackageLicenseConcluded: NOASSERTION @@ -27,6 +28,7 @@ SPDXID: SPDXRef-File FileType: OTHER FileType: SOURCE FileChecksum: SHA1: SOME-SHA1 +FileChecksum: SHA256: SOME-SHA256 LicenseConcluded: NOASSERTION LicenseInfoInFile: LGPL-2.1-or-later FileCopyrightText: NOASSERTION diff --git a/tests/data/doc_write/tv-simple.tv b/tests/data/doc_write/tv-simple.tv index 0ffa2833f..d7cd21426 100644 --- a/tests/data/doc_write/tv-simple.tv +++ b/tests/data/doc_write/tv-simple.tv @@ -12,6 +12,7 @@ PackageName: some/path SPDXID: SPDXRef-Package PackageDownloadLocation: NOASSERTION PackageChecksum: SHA1: SOME-SHA1 +PackageChecksum: SHA256: SOME-SHA256 PackageVerificationCode: SOME code PackageLicenseDeclared: NOASSERTION PackageLicenseConcluded: NOASSERTION @@ -27,6 +28,7 @@ SPDXID: SPDXRef-File FileType: OTHER FileType: SOURCE FileChecksum: SHA1: SOME-SHA1 +FileChecksum: SHA256: SOME-SHA256 LicenseConcluded: NOASSERTION LicenseInfoInFile: LGPL-2.1-only FileCopyrightText: NOASSERTION diff --git a/tests/data/doc_write/xml-simple-multi-package.xml b/tests/data/doc_write/xml-simple-multi-package.xml index 4b7e6e85c..7552c6dcc 100644 --- a/tests/data/doc_write/xml-simple-multi-package.xml +++ b/tests/data/doc_write/xml-simple-multi-package.xml @@ -56,4 +56,4 @@ LGPL-2.1-or-later NOASSERTION - \ No newline at end of file + diff --git a/tests/data/doc_write/xml-simple-plus.xml b/tests/data/doc_write/xml-simple-plus.xml index 7fb595fca..82f20efe7 100644 --- a/tests/data/doc_write/xml-simple-plus.xml +++ b/tests/data/doc_write/xml-simple-plus.xml @@ -18,6 +18,10 @@ SOME-SHA1 SHA1 + + SOME-SHA256 + SHA256 + NOASSERTION NOASSERTION LGPL-2.1-or-later @@ -34,6 +38,10 @@ SOME-SHA1 SHA1 + + SOME-SHA256 + SHA256 + NOASSERTION NOASSERTION LGPL-2.1-or-later diff --git a/tests/data/doc_write/xml-simple.xml b/tests/data/doc_write/xml-simple.xml index e10c36bc4..961fb9ad4 100644 --- a/tests/data/doc_write/xml-simple.xml +++ b/tests/data/doc_write/xml-simple.xml @@ -18,6 +18,10 @@ SOME-SHA1 SHA1 + + SOME-SHA256 + SHA256 + NOASSERTION NOASSERTION LGPL-2.1-only @@ -34,6 +38,10 @@ SOME-SHA1 SHA1 + + SOME-SHA256 + SHA256 + NOASSERTION NOASSERTION LGPL-2.1-only diff --git a/tests/data/doc_write/yaml-simple-plus.yaml b/tests/data/doc_write/yaml-simple-plus.yaml index a5391c072..007d6cdd9 100644 --- a/tests/data/doc_write/yaml-simple-plus.yaml +++ b/tests/data/doc_write/yaml-simple-plus.yaml @@ -13,6 +13,8 @@ files: checksums: - algorithm: SHA1 checksumValue: SOME-SHA1 + - algorithm: SHA256 + checksumValue: SOME-SHA256 copyrightText: NOASSERTION fileName: ./some/path/tofile licenseConcluded: NOASSERTION @@ -27,6 +29,8 @@ packages: checksums: - algorithm: SHA1 checksumValue: SOME-SHA1 + - algorithm: SHA256 + checksumValue: SOME-SHA256 copyrightText: Some copyright downloadLocation: NOASSERTION hasFiles: diff --git a/tests/data/doc_write/yaml-simple.yaml b/tests/data/doc_write/yaml-simple.yaml index 1ed7c6977..98e6edb42 100644 --- a/tests/data/doc_write/yaml-simple.yaml +++ b/tests/data/doc_write/yaml-simple.yaml @@ -13,6 +13,8 @@ files: checksums: - algorithm: SHA1 checksumValue: SOME-SHA1 + - algorithm: SHA256 + checksumValue: SOME-SHA256 copyrightText: NOASSERTION fileName: ./some/path/tofile licenseConcluded: NOASSERTION @@ -28,6 +30,8 @@ packages: checksums: - algorithm: SHA1 checksumValue: SOME-SHA1 + - algorithm: SHA256 + checksumValue: SOME-SHA256 copyrightText: Some copyright downloadLocation: NOASSERTION hasFiles: diff --git a/tests/data/formats/SPDXJsonExample.json b/tests/data/formats/SPDXJsonExample.json index 7d14fdace..c70308667 100644 --- a/tests/data/formats/SPDXJsonExample.json +++ b/tests/data/formats/SPDXJsonExample.json @@ -72,12 +72,16 @@ ], "licenseConcluded": "LicenseRef-1", "licenseComments": "This license is used by Jena", - "checksums": [ + "checksums": [ { - "checksumValue": "3ab4e1c67a2d28fced849ee1bb76e7391b93f125", - "algorithm": "SHA1" + "checksumValue": "3ab4e1c67a2d28fced849ee1bb76e7391b93f125", + "algorithm": "SHA1" + }, + { + "checksumValue": "3ab4e1c67a2d28fced849ee1bb76e7391b93f1250000000000000000", + "algorithm": "SHA256" } - ], + ], "fileTypes": [ "ARCHIVE", "OTHER" diff --git a/tests/data/formats/SPDXRdfExample.rdf b/tests/data/formats/SPDXRdfExample.rdf index 7446bc71a..d5912d8a8 100644 --- a/tests/data/formats/SPDXRdfExample.rdf +++ b/tests/data/formats/SPDXRdfExample.rdf @@ -90,6 +90,12 @@ + + + 3ab4e1c67a2d28fced849ee1bb76e7391b93f1250000000000000000 + + + @@ -135,6 +141,12 @@ + + + 2fd4e1c67a2d28fced849ee1bb76e7391b93eb120000000000000000 + + + src/org/spdx/parser/DOAPProject.java @@ -153,6 +165,12 @@ SpdxTranslatorSpdx.rdf + + + 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12 + + + diff --git a/tests/data/formats/SPDXSBOMExample.spdx.yml b/tests/data/formats/SPDXSBOMExample.spdx.yml index a5664c3eb..5655dae55 100644 --- a/tests/data/formats/SPDXSBOMExample.spdx.yml +++ b/tests/data/formats/SPDXSBOMExample.spdx.yml @@ -16,6 +16,9 @@ documentDescribes: packages: - SPDXID: "SPDXRef-Package-xyz" summary: "Awesome product created by Example Inc." + checksums: + - algorithm: SHA1 + checksumValue: SOME-SHA1 copyrightText: "copyright 2004-2020 Example Inc. All Rights Reserved." downloadLocation: "git+ssh://gitlab.example.com:3389/products/xyz.git@b2c358080011af6a366d2512a25a379fbe7b1f78" filesAnalyzed: false @@ -28,6 +31,9 @@ packages: description: "A command line tool and library for transferring data with URL syntax, supporting \ HTTP, HTTPS, FTP, FTPS, GOPHER, TFTP, SCP, SFTP, SMB, TELNET, DICT, LDAP, LDAPS, MQTT, FILE, \ IMAP, SMTP, POP3, RTSP and RTMP. libcurl offers a myriad of powerful features." + checksums: + - algorithm: SHA1 + checksumValue: SOME-SHA1 copyrightText: "Copyright (c) 1996 - 2020, Daniel Stenberg, , and many contributors, see the THANKS file." downloadLocation: "https://github.com/curl/curl/releases/download/curl-7_70_0/curl-7.70.0.tar.gz" @@ -41,6 +47,9 @@ packages: - SPDXID: "SPDXRef-Package-openssl" description: "OpenSSL is a robust, commercial-grade, full-featured Open Source Toolkit for the Transport Layer Security (TLS) protocol formerly known as the Secure Sockets Layer (SSL) protocol. The protocol implementation is based on a full-strength general purpose cryptographic library, which can also be used stand-alone." copyrightText: "copyright 2004-2020 The OpenSSL Project Authors. All Rights Reserved." + checksums: + - algorithm: SHA1 + checksumValue: SOME-SHA1 downloadLocation: "git+ssh://github.com/openssl/openssl.git@e2e09d9fba1187f8d6aafaa34d4172f56f1ffb72" filesAnalyzed: false homepage: "https://www.openssl.org/" diff --git a/tests/data/formats/SPDXTagExample.tag b/tests/data/formats/SPDXTagExample.tag index 4a886bea1..855ba417a 100644 --- a/tests/data/formats/SPDXTagExample.tag +++ b/tests/data/formats/SPDXTagExample.tag @@ -73,7 +73,8 @@ FileName: src/org/spdx/parser/DOAPProject.java SPDXID: SPDXRef-File1 FileType: SOURCE FileType: TEXT -FileChecksum: SHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12 +FileChecksum: SHA1: 2fd4e1c67a2d28fced849ee1bb76e7391b93eB12 +FileChecksum: SHA256: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb120000000000000000 LicenseConcluded: Apache-2.0 LicenseInfoInFile: Apache-2.0 FileCopyrightText: Copyright 2010, 2011 Source Auditor Inc. @@ -83,6 +84,7 @@ SPDXID: SPDXRef-File2 FileType: ARCHIVE FileType: OTHER FileChecksum: SHA1: 3ab4e1c67a2d28fced849ee1bb76e7391b93f125 +FileChecksum: SHA256: 3ab4e1c67a2d28fced849ee1bb76e7391b93f1250000000000000000 LicenseConcluded: LicenseRef-1 LicenseInfoInFile: LicenseRef-1 LicenseComments: This license is used by Jena diff --git a/tests/data/formats/SPDXXmlExample.xml b/tests/data/formats/SPDXXmlExample.xml index f31894e17..b703fb5f2 100644 --- a/tests/data/formats/SPDXXmlExample.xml +++ b/tests/data/formats/SPDXXmlExample.xml @@ -31,7 +31,7 @@ Organization: Linux Foundation 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12 - checksumAlgorithm_sha1 + SHA1 Version 0.9.2 (LicenseRef-3 AND LicenseRef-2 AND Apache-2.0 AND MPL-1.1 AND LicenseRef-1 AND LicenseRef-4) @@ -53,7 +53,7 @@ d6a770ba38583ed4bb4525bd96e50461655d2759 - checksumAlgorithm_sha1 + SHA1 https://spdx.org/spdxdocs/spdx-tools-v2.1-3F2504E0-4F89-41D3-9A0C-0305E82C3301 DocumentRef-spdx-tool-2.1 @@ -206,7 +206,7 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12 - checksumAlgorithm_sha1 + SHA1 SOURCE TEXT @@ -226,7 +226,11 @@ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. This license is used by Jena 3ab4e1c67a2d28fced849ee1bb76e7391b93f125 - checksumAlgorithm_sha1 + SHA1 + + + 3ab4e1c67a2d28fced849ee1bb76e7391b93f1250000000000000000 + SHA256 ARCHIVE OTHER diff --git a/tests/data/formats/SPDXYamlExample.yaml b/tests/data/formats/SPDXYamlExample.yaml index 96a8fe505..0aeec67e0 100644 --- a/tests/data/formats/SPDXYamlExample.yaml +++ b/tests/data/formats/SPDXYamlExample.yaml @@ -20,7 +20,7 @@ Document: packages: - SPDXID: SPDXRef-Package checksums: - - algorithm: checksumAlgorithm_sha1 + - algorithm: SHA1 checksumValue: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12 copyrightText: ' Copyright 2010, 2011 Source Auditor Inc.' description: This utility translates and SPDX RDF XML document to a spreadsheet, @@ -68,7 +68,7 @@ Document: validUntilDate: '2022-01-01T12:00:00Z' externalDocumentRefs: - checksum: - algorithm: checksumAlgorithm_sha1 + algorithm: SHA1 checksumValue: d6a770ba38583ed4bb4525bd96e50461655d2759 externalDocumentId: DocumentRef-spdx-tool-2.1 spdxDocument: https://spdx.org/spdxdocs/spdx-tools-v2.1-3F2504E0-4F89-41D3-9A0C-0305E82C3301 @@ -213,8 +213,10 @@ Document: files: - SPDXID: SPDXRef-File1 checksums: - - algorithm: checksumAlgorithm_sha1 + - algorithm: SHA1 checksumValue: 3ab4e1c67a2d28fced849ee1bb76e7391b93f125 + - algorithm: SHA256 + checksumValue: 3ab4e1c67a2d28fced849ee1bb76e7391b93f1250000000000000000 comment: This file belongs to Jena copyrightText: (c) Copyright 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007, 2008, 2009 Hewlett-Packard Development Company, LP @@ -233,7 +235,7 @@ Document: - SPDXID: SPDXRef-File2 checksums: - - algorithm: checksumAlgorithm_sha1 + - algorithm: SHA1 checksumValue: 2fd4e1c67a2d28fced849ee1bb76e7391b93eb12 copyrightText: Copyright 2010, 2011 Source Auditor Inc. fileTypes: diff --git a/tests/test_builder.py b/tests/test_builder.py index 30719e6a6..810d431e0 100644 --- a/tests/test_builder.py +++ b/tests/test_builder.py @@ -131,7 +131,7 @@ def test_checksum(self): chksum_val = "d6a770ba38583ed4bb4525bd96e50461655d2759" self.builder.set_ext_doc_id(self.document, "DocumentRef-spdx-tool-2.1") self.builder.set_chksum(self.document, chksum) - assert self.document.ext_document_references[-1].check_sum.value == chksum_val + assert self.document.ext_document_references[-1].checksum.value == chksum_val def test_add_ext_doc_refs(self): ext_doc_id_val = "DocumentRef-spdx-tool-2.1" @@ -149,7 +149,7 @@ def test_add_ext_doc_refs(self): assert ( self.document.ext_document_references[-1].spdx_document_uri == spdx_doc_uri ) - assert self.document.ext_document_references[-1].check_sum.value == chksum_val + assert self.document.ext_document_references[-1].checksum.value == chksum_val class TestEntityBuilder(TestCase): @@ -516,7 +516,7 @@ def test_pkg_verif_order(self): @testing_utils.raises(builders.OrderError) def test_pkg_chksum_order(self): - self.builder.set_pkg_chk_sum(self.document, "some code") + self.builder.set_pkg_checksum(self.document, "some code") @testing_utils.raises(builders.OrderError) def test_pkg_source_info_order(self): diff --git a/tests/test_checksum.py b/tests/test_checksum.py new file mode 100644 index 000000000..ebaaaba2a --- /dev/null +++ b/tests/test_checksum.py @@ -0,0 +1,48 @@ +# Copyright (c) 2022 spdx contributors +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# http://www.apache.org/licenses/LICENSE-2.0 +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +import pytest + +from spdx.checksum import ChecksumAlgorithm + + +@pytest.mark.parametrize("algorithm,expected", + [("SHA1", "checksumAlgorithm_sha1"), ("SHA224", "checksumAlgorithm_sha224"), + ("SHA3_256", "checksumAlgorithm_sha3_256"), ("BLAKE2B_256", "checksumAlgorithm_blake2b256"), + ("MD5", "checksumAlgorithm_md5")]) +def test_checksum_to_rdf(algorithm, expected): + test_algorithm = ChecksumAlgorithm[algorithm] + rdf_algorithm = test_algorithm.algorithm_to_rdf_representation() + + assert rdf_algorithm == expected + + +@pytest.mark.parametrize("expected,rdf_algorithm", + [(ChecksumAlgorithm.SHA1, "checksumAlgorithm_sha1"), + (ChecksumAlgorithm.SHA224, "checksumAlgorithm_sha224"), + (ChecksumAlgorithm.SHA3_256, "checksumAlgorithm_sha3_256"), + (ChecksumAlgorithm.BLAKE2B_256, "checksumAlgorithm_blake2b256"), + (ChecksumAlgorithm.MD5, "checksumAlgorithm_md5")]) +def test_checksum_from_rdf(rdf_algorithm, expected): + algorithm = ChecksumAlgorithm.checksum_from_rdf(rdf_algorithm) + + assert algorithm == expected + + +@pytest.mark.parametrize("rdf_algorithm", + ["_checksumAlgorithm_sha1", "checksumAlgorithm_sha_224", "checksumAlgorithm_sha3256", + "checksumAlgorithm_blake2b 256", "checksumAlgorithm_blake2b-256", + "checksumAlgorithm_bblake2b 256"]) +def test_checksum_from_wrong_rdf(rdf_algorithm): + with pytest.raises(ValueError) as error: + ChecksumAlgorithm.checksum_from_rdf(rdf_algorithm) + + assert str(error.value).startswith("Invalid algorithm for checksum") + diff --git a/tests/test_document.py b/tests/test_document.py index 4d1a93027..7f9a1d677 100644 --- a/tests/test_document.py +++ b/tests/test_document.py @@ -16,7 +16,7 @@ from datetime import datetime from unittest import TestCase -from spdx.checksum import Algorithm +from spdx.checksum import Checksum, ChecksumAlgorithm from spdx.config import LICENSE_MAP, EXCEPTION_MAP from spdx.creationinfo import Tool from spdx.document import Document, ExternalDocumentRef @@ -65,26 +65,26 @@ def test_creation(self): document.add_ext_document_reference( ExternalDocumentRef('DocumentRef-spdx-tool-2.1', 'https://spdx.org/spdxdocs/spdx-tools-v2.1-3F2504E0-4F89-41D3-9A0C-0305E82C3301', - Algorithm('SHA1', 'SOME-SHA1')) + Checksum(ChecksumAlgorithm.SHA1, 'SOME-SHA1')) ) assert document.comment is None assert document.version == Version(2, 1) assert document.data_license.identifier == 'AFL-1.1' assert document.ext_document_references[-1].external_document_id == 'DocumentRef-spdx-tool-2.1' assert document.ext_document_references[-1].spdx_document_uri == 'https://spdx.org/spdxdocs/spdx-tools-v2.1-3F2504E0-4F89-41D3-9A0C-0305E82C3301' - assert document.ext_document_references[-1].check_sum.identifier == 'SHA1' - assert document.ext_document_references[-1].check_sum.value == 'SOME-SHA1' + assert document.ext_document_references[-1].checksum.identifier.name == 'SHA1' + assert document.ext_document_references[-1].checksum.value == 'SOME-SHA1' def test_document_validate_failures_returns_informative_messages(self): doc = Document(Version(2, 1), License.from_identifier('CC0-1.0'), 'Sample_Document-V2.1', spdx_id='SPDXRef-DOCUMENT', namespace='https://spdx.org/spdxdocs/spdx-example-444504E0-4F89-41D3-9A0C-0305E82C3301') pack = doc.package = Package('some/path', NoAssert()) - pack.checksum = 'SOME-SHA1' + pack.set_checksum(Checksum(ChecksumAlgorithm.SHA256, 'SOME-SHA256')) file1 = File('./some/path/tofile') file1.name = './some/path/tofile' file1.spdx_id = 'SPDXRef-File' - file1.chksum = Algorithm('SHA1', 'SOME-SHA1') + file1.set_checksum(Checksum(ChecksumAlgorithm.SHA1, 'SOME-SHA1')) lic1 = License.from_identifier('LGPL-2.1-only') file1.add_lics(lic1) pack.add_lics_from_file(lic1) @@ -92,8 +92,6 @@ def test_document_validate_failures_returns_informative_messages(self): messages = doc.validate(messages) expected = ['Sample_Document-V2.1: Creation info missing created date.', 'Sample_Document-V2.1: No creators defined, must have at least one.', - 'Sample_Document-V2.1: some/path: Package checksum must be instance of ' - 'spdx.checksum.Algorithm', 'Sample_Document-V2.1: some/path: Package download_location can not be None.'] assert sorted(expected) == sorted(messages) @@ -107,6 +105,7 @@ def test_document_is_valid_when_using_or_later_licenses(self): package = doc.package = Package(name='some/path', download_location=NoAssert()) package.spdx_id = 'SPDXRef-Package' package.cr_text = 'Some copyright' + package.set_checksum(Checksum(ChecksumAlgorithm.SHA1, 'SOME-SHA1')) package.verif_code = 'SOME code' package.license_declared = NoAssert() package.conc_lics = NoAssert() @@ -115,7 +114,7 @@ def test_document_is_valid_when_using_or_later_licenses(self): file1.name = './some/path/tofile' file1.spdx_id = 'SPDXRef-File' file1.file_types = [FileType.OTHER] - file1.chksum = Algorithm('SHA1', 'SOME-SHA1') + file1.set_checksum(Checksum(ChecksumAlgorithm.SHA1, 'SOME-SHA1')) file1.conc_lics = NoAssert() file1.copyright = NoAssert() @@ -179,7 +178,8 @@ def _get_lgpl_doc(self, or_later=False): package.spdx_id = 'SPDXRef-Package' package.cr_text = 'Some copyright' package.verif_code = 'SOME code' - package.checksum = Algorithm('SHA1', 'SOME-SHA1') + package.set_checksum(Checksum(ChecksumAlgorithm.SHA1, 'SOME-SHA1')) + package.set_checksum(Checksum(ChecksumAlgorithm.SHA256, 'SOME-SHA256')) package.license_declared = NoAssert() package.conc_lics = NoAssert() package.primary_package_purpose = PackagePurpose.FILE @@ -191,7 +191,8 @@ def _get_lgpl_doc(self, or_later=False): file1 = File('./some/path/tofile') file1.name = './some/path/tofile' file1.spdx_id = 'SPDXRef-File' - file1.chksum = Algorithm('SHA1', 'SOME-SHA1') + file1.set_checksum(Checksum(ChecksumAlgorithm.SHA1, 'SOME-SHA1')) + file1.set_checksum(Checksum(ChecksumAlgorithm.SHA256, 'SOME-SHA256')) file1.conc_lics = NoAssert() file1.copyright = NoAssert() file1.file_types = [FileType.OTHER, FileType.SOURCE] @@ -245,7 +246,7 @@ def _get_lgpl_multi_package_doc(self, or_later=False): file1 = File('./some/path/tofile') file1.name = './some/path/tofile' file1.spdx_id = 'SPDXRef-File' - file1.chksum = Algorithm('SHA1', 'SOME-SHA1') + file1.set_checksum(Checksum(ChecksumAlgorithm.SHA1, 'SOME-SHA1')) file1.conc_lics = NoAssert() file1.copyright = NoAssert() diff --git a/tests/test_jsonyamlxml_writer.py b/tests/test_jsonyamlxml_writer.py index 291ed1386..0ed226e36 100644 --- a/tests/test_jsonyamlxml_writer.py +++ b/tests/test_jsonyamlxml_writer.py @@ -5,7 +5,7 @@ import pytest -from spdx.checksum import Algorithm +from spdx.checksum import Checksum, ChecksumAlgorithm from spdx.document import Document from spdx.file import File from spdx.license import License @@ -147,7 +147,8 @@ def minimal_document(): def minimal_file(): - file = File(name="Example File", spdx_id="SPDXRef-File", chksum=Algorithm('SHA1', 'SOME-SHA1')) + file = File(name="Example File", spdx_id="SPDXRef-File") + file.set_checksum(Checksum(ChecksumAlgorithm.SHA1, 'some-sha1-value')) return file diff --git a/tests/test_package.py b/tests/test_package.py index 8438a5503..3b8325b02 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -12,7 +12,7 @@ import unittest from unittest import TestCase -from spdx.checksum import Algorithm +from spdx.checksum import Checksum from spdx.package import Package @@ -25,11 +25,11 @@ def test_calc_verif_code(self): def test_package_with_non_sha1_check_sum(self): package = Package() - package.checksum = Algorithm("SHA256", '') + package.set_checksum(Checksum("SHA256", '')) # Make sure that validation still works despite the checksum not being SHA1 messages = [] - messages = package.validate_checksum(messages) + package.validate_checksums(messages) if __name__ == '__main__': diff --git a/tests/utils_test.py b/tests/utils_test.py index e9b4b09c7..c196168f1 100644 --- a/tests/utils_test.py +++ b/tests/utils_test.py @@ -331,7 +331,7 @@ def checksum_to_dict(cls, checksum): if checksum is None: return None return OrderedDict([ - ('identifier', checksum.identifier), + ('identifier', checksum.identifier.name), ('value', checksum.value)]) @classmethod @@ -358,7 +358,7 @@ def package_to_dict(cls, package): ('licenseDeclared', cls.license_to_dict(package.license_declared)), ('copyrightText', package.cr_text), ('licenseComment', package.license_comment), - ('checksum', cls.checksum_to_dict(package.checksum)), + ('checksums', [cls.checksum_to_dict(checksum) for checksum in package.checksums.values()]), ('licenseInfoFromFiles', [cls.license_to_dict(lic) for lic in lics_from_files]), ('verificationCode', OrderedDict([ ('value', package.verif_code), @@ -368,10 +368,10 @@ def package_to_dict(cls, package): if package.built_date: package_dict['builtDate'] = utils.datetime_iso_format(package.built_date) - + if package.release_date: package_dict['releaseDate'] = utils.datetime_iso_format(package.release_date) - + if package.valid_until_date: package_dict['validUntilDate'] = utils.datetime_iso_format(package.valid_until_date) @@ -391,6 +391,10 @@ def files_to_list(cls, files): for file in files: lics_in_files = sorted(file.licenses_in_file, key=lambda lic: lic.identifier) contributors = sorted(file.contributors, key=lambda c: c.name) + chk_sums = [] + for checksum in file.checksums.values(): + chk_sums.append(cls.checksum_to_dict(checksum)) + file_dict = OrderedDict([ ('id', file.spdx_id), ('fileName', file.name), @@ -400,7 +404,7 @@ def files_to_list(cls, files): ('copyrightText', file.copyright), ('licenseComment', file.license_comment), ('notice', file.notice), - ('checksum', cls.checksum_to_dict(file.chksum)), + ('checksums', chk_sums), ('licenseInfoInFiles', [cls.license_to_dict(lic) for lic in lics_in_files]), ('contributors', [cls.entity_to_dict(contributor) for contributor in contributors]), ('dependencies', sorted(file.dependencies)), @@ -423,7 +427,7 @@ def ext_document_references_to_list(cls, ext_doc_refs): ext_doc_ref_dict = OrderedDict([ ('externalDocumentId', ext_doc_ref.external_document_id), ('spdxDocument', ext_doc_ref.spdx_document_uri), - ('checksum', cls.checksum_to_dict(ext_doc_ref.check_sum)), + ('checksum', cls.checksum_to_dict(ext_doc_ref.checksum)), ]) ext_doc_refs_list.append(ext_doc_ref_dict)