1*9e94795aSAndroid Build Coastguard Worker#!/usr/bin/env python3 2*9e94795aSAndroid Build Coastguard Worker# 3*9e94795aSAndroid Build Coastguard Worker# Copyright (C) 2023 The Android Open Source Project 4*9e94795aSAndroid Build Coastguard Worker# 5*9e94795aSAndroid Build Coastguard Worker# Licensed under the Apache License, Version 2.0 (the "License"); 6*9e94795aSAndroid Build Coastguard Worker# you may not use this file except in compliance with the License. 7*9e94795aSAndroid Build Coastguard Worker# You may obtain a copy of the License at 8*9e94795aSAndroid Build Coastguard Worker# 9*9e94795aSAndroid Build Coastguard Worker# http://www.apache.org/licenses/LICENSE-2.0 10*9e94795aSAndroid Build Coastguard Worker# 11*9e94795aSAndroid Build Coastguard Worker# Unless required by applicable law or agreed to in writing, software 12*9e94795aSAndroid Build Coastguard Worker# distributed under the License is distributed on an "AS IS" BASIS, 13*9e94795aSAndroid Build Coastguard Worker# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14*9e94795aSAndroid Build Coastguard Worker# See the License for the specific language governing permissions and 15*9e94795aSAndroid Build Coastguard Worker# limitations under the License. 16*9e94795aSAndroid Build Coastguard Worker 17*9e94795aSAndroid Build Coastguard Workerimport io 18*9e94795aSAndroid Build Coastguard Workerimport pathlib 19*9e94795aSAndroid Build Coastguard Workerimport unittest 20*9e94795aSAndroid Build Coastguard Workerimport sbom_data 21*9e94795aSAndroid Build Coastguard Workerimport sbom_writers 22*9e94795aSAndroid Build Coastguard Worker 23*9e94795aSAndroid Build Coastguard WorkerBUILD_FINGER_PRINT = 'build_finger_print' 24*9e94795aSAndroid Build Coastguard WorkerSUPPLIER_GOOGLE = 'Organization: Google' 25*9e94795aSAndroid Build Coastguard WorkerSUPPLIER_UPSTREAM = 'Organization: upstream' 26*9e94795aSAndroid Build Coastguard Worker 27*9e94795aSAndroid Build Coastguard WorkerSPDXID_PREBUILT_PACKAGE1 = 'SPDXRef-PREBUILT-package1' 28*9e94795aSAndroid Build Coastguard WorkerSPDXID_SOURCE_PACKAGE1 = 'SPDXRef-SOURCE-package1' 29*9e94795aSAndroid Build Coastguard WorkerSPDXID_UPSTREAM_PACKAGE1 = 'SPDXRef-UPSTREAM-package1' 30*9e94795aSAndroid Build Coastguard Worker 31*9e94795aSAndroid Build Coastguard WorkerSPDXID_FILE1 = 'SPDXRef-file1' 32*9e94795aSAndroid Build Coastguard WorkerSPDXID_FILE2 = 'SPDXRef-file2' 33*9e94795aSAndroid Build Coastguard WorkerSPDXID_FILE3 = 'SPDXRef-file3' 34*9e94795aSAndroid Build Coastguard WorkerSPDXID_FILE4 = 'SPDXRef-file4' 35*9e94795aSAndroid Build Coastguard Worker 36*9e94795aSAndroid Build Coastguard WorkerSPDXID_LICENSE_1 = 'LicenseRef-Android-License-1' 37*9e94795aSAndroid Build Coastguard WorkerSPDXID_LICENSE_2 = 'LicenseRef-Android-License-2' 38*9e94795aSAndroid Build Coastguard WorkerSPDXID_LICENSE_3 = 'LicenseRef-Android-License-3' 39*9e94795aSAndroid Build Coastguard Worker 40*9e94795aSAndroid Build Coastguard WorkerLICENSE_APACHE_TEXT = "LICENSE_APACHE" 41*9e94795aSAndroid Build Coastguard WorkerLICENSE1_TEXT = 'LICENSE 1' 42*9e94795aSAndroid Build Coastguard WorkerLICENSE2_TEXT = 'LICENSE 2' 43*9e94795aSAndroid Build Coastguard WorkerLICENSE3_TEXT = 'LICENSE 3' 44*9e94795aSAndroid Build Coastguard Worker 45*9e94795aSAndroid Build Coastguard Workerclass SBOMWritersTest(unittest.TestCase): 46*9e94795aSAndroid Build Coastguard Worker 47*9e94795aSAndroid Build Coastguard Worker def setUp(self): 48*9e94795aSAndroid Build Coastguard Worker # SBOM of a product 49*9e94795aSAndroid Build Coastguard Worker self.sbom_doc = sbom_data.Document(name='test doc', 50*9e94795aSAndroid Build Coastguard Worker namespace='http://www.google.com/sbom/spdx/android', 51*9e94795aSAndroid Build Coastguard Worker creators=[SUPPLIER_GOOGLE], 52*9e94795aSAndroid Build Coastguard Worker created='2023-03-31T22:17:58Z', 53*9e94795aSAndroid Build Coastguard Worker describes=sbom_data.SPDXID_PRODUCT) 54*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_external_ref( 55*9e94795aSAndroid Build Coastguard Worker sbom_data.DocumentExternalReference(id='DocumentRef-external_doc_ref', 56*9e94795aSAndroid Build Coastguard Worker uri='external_doc_uri', 57*9e94795aSAndroid Build Coastguard Worker checksum='SHA1: 1234567890')) 58*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_package( 59*9e94795aSAndroid Build Coastguard Worker sbom_data.Package(id=sbom_data.SPDXID_PRODUCT, 60*9e94795aSAndroid Build Coastguard Worker name=sbom_data.PACKAGE_NAME_PRODUCT, 61*9e94795aSAndroid Build Coastguard Worker download_location=sbom_data.VALUE_NONE, 62*9e94795aSAndroid Build Coastguard Worker supplier=SUPPLIER_GOOGLE, 63*9e94795aSAndroid Build Coastguard Worker version=BUILD_FINGER_PRINT, 64*9e94795aSAndroid Build Coastguard Worker files_analyzed=True, 65*9e94795aSAndroid Build Coastguard Worker verification_code='123456', 66*9e94795aSAndroid Build Coastguard Worker file_ids=[SPDXID_FILE1, SPDXID_FILE2, SPDXID_FILE3])) 67*9e94795aSAndroid Build Coastguard Worker 68*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_package( 69*9e94795aSAndroid Build Coastguard Worker sbom_data.Package(id=sbom_data.SPDXID_PLATFORM, 70*9e94795aSAndroid Build Coastguard Worker name=sbom_data.PACKAGE_NAME_PLATFORM, 71*9e94795aSAndroid Build Coastguard Worker download_location=sbom_data.VALUE_NONE, 72*9e94795aSAndroid Build Coastguard Worker supplier=SUPPLIER_GOOGLE, 73*9e94795aSAndroid Build Coastguard Worker version=BUILD_FINGER_PRINT, 74*9e94795aSAndroid Build Coastguard Worker declared_license_ids=[sbom_data.SPDXID_LICENSE_APACHE] 75*9e94795aSAndroid Build Coastguard Worker )) 76*9e94795aSAndroid Build Coastguard Worker 77*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_package( 78*9e94795aSAndroid Build Coastguard Worker sbom_data.Package(id=SPDXID_PREBUILT_PACKAGE1, 79*9e94795aSAndroid Build Coastguard Worker name='Prebuilt package1', 80*9e94795aSAndroid Build Coastguard Worker download_location=sbom_data.VALUE_NONE, 81*9e94795aSAndroid Build Coastguard Worker supplier=SUPPLIER_GOOGLE, 82*9e94795aSAndroid Build Coastguard Worker version=BUILD_FINGER_PRINT, 83*9e94795aSAndroid Build Coastguard Worker declared_license_ids=[SPDXID_LICENSE_1], 84*9e94795aSAndroid Build Coastguard Worker )) 85*9e94795aSAndroid Build Coastguard Worker 86*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_package( 87*9e94795aSAndroid Build Coastguard Worker sbom_data.Package(id=SPDXID_SOURCE_PACKAGE1, 88*9e94795aSAndroid Build Coastguard Worker name='Source package1', 89*9e94795aSAndroid Build Coastguard Worker download_location=sbom_data.VALUE_NONE, 90*9e94795aSAndroid Build Coastguard Worker supplier=SUPPLIER_GOOGLE, 91*9e94795aSAndroid Build Coastguard Worker version=BUILD_FINGER_PRINT, 92*9e94795aSAndroid Build Coastguard Worker declared_license_ids=[SPDXID_LICENSE_2, SPDXID_LICENSE_3], 93*9e94795aSAndroid Build Coastguard Worker external_refs=[sbom_data.PackageExternalRef( 94*9e94795aSAndroid Build Coastguard Worker category=sbom_data.PackageExternalRefCategory.SECURITY, 95*9e94795aSAndroid Build Coastguard Worker type=sbom_data.PackageExternalRefType.cpe22Type, 96*9e94795aSAndroid Build Coastguard Worker locator='cpe:/a:jsoncpp_project:jsoncpp:1.9.4')] 97*9e94795aSAndroid Build Coastguard Worker )) 98*9e94795aSAndroid Build Coastguard Worker 99*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_package( 100*9e94795aSAndroid Build Coastguard Worker sbom_data.Package(id=SPDXID_UPSTREAM_PACKAGE1, 101*9e94795aSAndroid Build Coastguard Worker name='Upstream package1', 102*9e94795aSAndroid Build Coastguard Worker supplier=SUPPLIER_UPSTREAM, 103*9e94795aSAndroid Build Coastguard Worker version='1.1', 104*9e94795aSAndroid Build Coastguard Worker declared_license_ids=[SPDXID_LICENSE_2, SPDXID_LICENSE_3], 105*9e94795aSAndroid Build Coastguard Worker )) 106*9e94795aSAndroid Build Coastguard Worker 107*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_SOURCE_PACKAGE1, 108*9e94795aSAndroid Build Coastguard Worker relationship=sbom_data.RelationshipType.VARIANT_OF, 109*9e94795aSAndroid Build Coastguard Worker id2=SPDXID_UPSTREAM_PACKAGE1)) 110*9e94795aSAndroid Build Coastguard Worker 111*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.files.append( 112*9e94795aSAndroid Build Coastguard Worker sbom_data.File(id=SPDXID_FILE1, name='/bin/file1', checksum='SHA1: 11111', concluded_license_ids=[sbom_data.SPDXID_LICENSE_APACHE])) 113*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.files.append( 114*9e94795aSAndroid Build Coastguard Worker sbom_data.File(id=SPDXID_FILE2, name='/bin/file2', checksum='SHA1: 22222', concluded_license_ids=[SPDXID_LICENSE_1])) 115*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.files.append( 116*9e94795aSAndroid Build Coastguard Worker sbom_data.File(id=SPDXID_FILE3, name='/bin/file3', checksum='SHA1: 33333', concluded_license_ids=[SPDXID_LICENSE_2, SPDXID_LICENSE_3])) 117*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.files.append( 118*9e94795aSAndroid Build Coastguard Worker sbom_data.File(id=SPDXID_FILE4, name='file4.a', checksum='SHA1: 44444')) 119*9e94795aSAndroid Build Coastguard Worker 120*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 121*9e94795aSAndroid Build Coastguard Worker relationship=sbom_data.RelationshipType.GENERATED_FROM, 122*9e94795aSAndroid Build Coastguard Worker id2=sbom_data.SPDXID_PLATFORM)) 123*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE2, 124*9e94795aSAndroid Build Coastguard Worker relationship=sbom_data.RelationshipType.GENERATED_FROM, 125*9e94795aSAndroid Build Coastguard Worker id2=SPDXID_PREBUILT_PACKAGE1)) 126*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE3, 127*9e94795aSAndroid Build Coastguard Worker relationship=sbom_data.RelationshipType.GENERATED_FROM, 128*9e94795aSAndroid Build Coastguard Worker id2=SPDXID_SOURCE_PACKAGE1 129*9e94795aSAndroid Build Coastguard Worker )) 130*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 131*9e94795aSAndroid Build Coastguard Worker relationship=sbom_data.RelationshipType.STATIC_LINK, 132*9e94795aSAndroid Build Coastguard Worker id2=SPDXID_FILE4 133*9e94795aSAndroid Build Coastguard Worker )) 134*9e94795aSAndroid Build Coastguard Worker 135*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_license(sbom_data.License(sbom_data.SPDXID_LICENSE_APACHE, LICENSE_APACHE_TEXT, "License-Apache")) 136*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_license(sbom_data.License(SPDXID_LICENSE_1, LICENSE1_TEXT, "License-1")) 137*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_license(sbom_data.License(SPDXID_LICENSE_2, LICENSE2_TEXT, "License-2")) 138*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.add_license(sbom_data.License(SPDXID_LICENSE_3, LICENSE3_TEXT, "License-3")) 139*9e94795aSAndroid Build Coastguard Worker 140*9e94795aSAndroid Build Coastguard Worker # SBOM fragment of a APK 141*9e94795aSAndroid Build Coastguard Worker self.unbundled_sbom_doc = sbom_data.Document(name='test doc', 142*9e94795aSAndroid Build Coastguard Worker namespace='http://www.google.com/sbom/spdx/android', 143*9e94795aSAndroid Build Coastguard Worker creators=[SUPPLIER_GOOGLE], 144*9e94795aSAndroid Build Coastguard Worker created='2023-03-31T22:17:58Z', 145*9e94795aSAndroid Build Coastguard Worker describes=SPDXID_FILE1) 146*9e94795aSAndroid Build Coastguard Worker 147*9e94795aSAndroid Build Coastguard Worker self.unbundled_sbom_doc.files.append( 148*9e94795aSAndroid Build Coastguard Worker sbom_data.File(id=SPDXID_FILE1, name='/bin/file1.apk', checksum='SHA1: 11111')) 149*9e94795aSAndroid Build Coastguard Worker self.unbundled_sbom_doc.add_package( 150*9e94795aSAndroid Build Coastguard Worker sbom_data.Package(id=SPDXID_SOURCE_PACKAGE1, 151*9e94795aSAndroid Build Coastguard Worker name='Unbundled apk package', 152*9e94795aSAndroid Build Coastguard Worker download_location=sbom_data.VALUE_NONE, 153*9e94795aSAndroid Build Coastguard Worker supplier=SUPPLIER_GOOGLE, 154*9e94795aSAndroid Build Coastguard Worker version=BUILD_FINGER_PRINT)) 155*9e94795aSAndroid Build Coastguard Worker self.unbundled_sbom_doc.add_relationship(sbom_data.Relationship(id1=SPDXID_FILE1, 156*9e94795aSAndroid Build Coastguard Worker relationship=sbom_data.RelationshipType.GENERATED_FROM, 157*9e94795aSAndroid Build Coastguard Worker id2=SPDXID_SOURCE_PACKAGE1)) 158*9e94795aSAndroid Build Coastguard Worker 159*9e94795aSAndroid Build Coastguard Worker def test_tagvalue_writer(self): 160*9e94795aSAndroid Build Coastguard Worker with io.StringIO() as output: 161*9e94795aSAndroid Build Coastguard Worker sbom_writers.TagValueWriter.write(self.sbom_doc, output) 162*9e94795aSAndroid Build Coastguard Worker expected_output = pathlib.Path('testdata/expected_tagvalue_sbom.spdx').read_text() 163*9e94795aSAndroid Build Coastguard Worker self.maxDiff = None 164*9e94795aSAndroid Build Coastguard Worker self.assertEqual(expected_output, output.getvalue()) 165*9e94795aSAndroid Build Coastguard Worker 166*9e94795aSAndroid Build Coastguard Worker def test_tagvalue_writer_doc_describes_file(self): 167*9e94795aSAndroid Build Coastguard Worker with io.StringIO() as output: 168*9e94795aSAndroid Build Coastguard Worker self.sbom_doc.describes = SPDXID_FILE4 169*9e94795aSAndroid Build Coastguard Worker sbom_writers.TagValueWriter.write(self.sbom_doc, output) 170*9e94795aSAndroid Build Coastguard Worker expected_output = pathlib.Path('testdata/expected_tagvalue_sbom_doc_describes_file.spdx').read_text() 171*9e94795aSAndroid Build Coastguard Worker self.maxDiff = None 172*9e94795aSAndroid Build Coastguard Worker self.assertEqual(expected_output, output.getvalue()) 173*9e94795aSAndroid Build Coastguard Worker 174*9e94795aSAndroid Build Coastguard Worker def test_tagvalue_writer_unbundled(self): 175*9e94795aSAndroid Build Coastguard Worker with io.StringIO() as output: 176*9e94795aSAndroid Build Coastguard Worker sbom_writers.TagValueWriter.write(self.unbundled_sbom_doc, output, fragment=True) 177*9e94795aSAndroid Build Coastguard Worker expected_output = pathlib.Path('testdata/expected_tagvalue_sbom_unbundled.spdx').read_text() 178*9e94795aSAndroid Build Coastguard Worker self.maxDiff = None 179*9e94795aSAndroid Build Coastguard Worker self.assertEqual(expected_output, output.getvalue()) 180*9e94795aSAndroid Build Coastguard Worker 181*9e94795aSAndroid Build Coastguard Worker def test_json_writer(self): 182*9e94795aSAndroid Build Coastguard Worker with io.StringIO() as output: 183*9e94795aSAndroid Build Coastguard Worker sbom_writers.JSONWriter.write(self.sbom_doc, output) 184*9e94795aSAndroid Build Coastguard Worker expected_output = pathlib.Path('testdata/expected_json_sbom.spdx.json').read_text() 185*9e94795aSAndroid Build Coastguard Worker self.maxDiff = None 186*9e94795aSAndroid Build Coastguard Worker self.assertEqual(expected_output, output.getvalue()) 187*9e94795aSAndroid Build Coastguard Worker 188*9e94795aSAndroid Build Coastguard Worker 189*9e94795aSAndroid Build Coastguard Workerif __name__ == '__main__': 190*9e94795aSAndroid Build Coastguard Worker unittest.main(verbosity=2) 191