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 Worker""" 18*9e94795aSAndroid Build Coastguard WorkerDefine data classes that model SBOMs defined by SPDX. The data classes could be 19*9e94795aSAndroid Build Coastguard Workerwritten out to different formats (tagvalue, JSON, etc) of SPDX with corresponding 20*9e94795aSAndroid Build Coastguard Workerwriter utilities. 21*9e94795aSAndroid Build Coastguard Worker 22*9e94795aSAndroid Build Coastguard WorkerRrefer to SPDX 2.3 spec: https://spdx.github.io/spdx-spec/v2.3/ and go/android-spdx for details of 23*9e94795aSAndroid Build Coastguard Workerfields in each data class. 24*9e94795aSAndroid Build Coastguard Worker""" 25*9e94795aSAndroid Build Coastguard Worker 26*9e94795aSAndroid Build Coastguard Workerfrom dataclasses import dataclass, field 27*9e94795aSAndroid Build Coastguard Workerfrom typing import List 28*9e94795aSAndroid Build Coastguard Workerimport hashlib 29*9e94795aSAndroid Build Coastguard Worker 30*9e94795aSAndroid Build Coastguard WorkerSPDXID_DOC = 'SPDXRef-DOCUMENT' 31*9e94795aSAndroid Build Coastguard WorkerSPDXID_PRODUCT = 'SPDXRef-PRODUCT' 32*9e94795aSAndroid Build Coastguard WorkerSPDXID_PLATFORM = 'SPDXRef-PLATFORM' 33*9e94795aSAndroid Build Coastguard WorkerSPDXID_LICENSE_APACHE = 'LicenseRef-Android-Apache-2.0' 34*9e94795aSAndroid Build Coastguard Worker 35*9e94795aSAndroid Build Coastguard WorkerPACKAGE_NAME_PRODUCT = 'PRODUCT' 36*9e94795aSAndroid Build Coastguard WorkerPACKAGE_NAME_PLATFORM = 'PLATFORM' 37*9e94795aSAndroid Build Coastguard Worker 38*9e94795aSAndroid Build Coastguard WorkerVALUE_NOASSERTION = 'NOASSERTION' 39*9e94795aSAndroid Build Coastguard WorkerVALUE_NONE = 'NONE' 40*9e94795aSAndroid Build Coastguard Worker 41*9e94795aSAndroid Build Coastguard Worker 42*9e94795aSAndroid Build Coastguard Workerclass PackageExternalRefCategory: 43*9e94795aSAndroid Build Coastguard Worker SECURITY = 'SECURITY' 44*9e94795aSAndroid Build Coastguard Worker PACKAGE_MANAGER = 'PACKAGE-MANAGER' 45*9e94795aSAndroid Build Coastguard Worker PERSISTENT_ID = 'PERSISTENT-ID' 46*9e94795aSAndroid Build Coastguard Worker OTHER = 'OTHER' 47*9e94795aSAndroid Build Coastguard Worker 48*9e94795aSAndroid Build Coastguard Worker 49*9e94795aSAndroid Build Coastguard Workerclass PackageExternalRefType: 50*9e94795aSAndroid Build Coastguard Worker cpe22Type = 'cpe22Type' 51*9e94795aSAndroid Build Coastguard Worker cpe23Type = 'cpe23Type' 52*9e94795aSAndroid Build Coastguard Worker 53*9e94795aSAndroid Build Coastguard Worker 54*9e94795aSAndroid Build Coastguard Worker@dataclass(frozen=True) 55*9e94795aSAndroid Build Coastguard Workerclass PackageExternalRef: 56*9e94795aSAndroid Build Coastguard Worker category: PackageExternalRefCategory 57*9e94795aSAndroid Build Coastguard Worker type: PackageExternalRefType 58*9e94795aSAndroid Build Coastguard Worker locator: str 59*9e94795aSAndroid Build Coastguard Worker 60*9e94795aSAndroid Build Coastguard Worker 61*9e94795aSAndroid Build Coastguard Worker@dataclass 62*9e94795aSAndroid Build Coastguard Workerclass Package: 63*9e94795aSAndroid Build Coastguard Worker name: str 64*9e94795aSAndroid Build Coastguard Worker id: str 65*9e94795aSAndroid Build Coastguard Worker version: str = None 66*9e94795aSAndroid Build Coastguard Worker supplier: str = None 67*9e94795aSAndroid Build Coastguard Worker download_location: str = None 68*9e94795aSAndroid Build Coastguard Worker files_analyzed: bool = False 69*9e94795aSAndroid Build Coastguard Worker verification_code: str = None 70*9e94795aSAndroid Build Coastguard Worker file_ids: List[str] = field(default_factory=list) 71*9e94795aSAndroid Build Coastguard Worker external_refs: List[PackageExternalRef] = field(default_factory=list) 72*9e94795aSAndroid Build Coastguard Worker declared_license_ids: List[str] = field(default_factory=list) 73*9e94795aSAndroid Build Coastguard Worker 74*9e94795aSAndroid Build Coastguard Worker 75*9e94795aSAndroid Build Coastguard Worker@dataclass 76*9e94795aSAndroid Build Coastguard Workerclass File: 77*9e94795aSAndroid Build Coastguard Worker id: str 78*9e94795aSAndroid Build Coastguard Worker name: str 79*9e94795aSAndroid Build Coastguard Worker checksum: str 80*9e94795aSAndroid Build Coastguard Worker concluded_license_ids: List[str] = field(default_factory=list) 81*9e94795aSAndroid Build Coastguard Worker 82*9e94795aSAndroid Build Coastguard Worker 83*9e94795aSAndroid Build Coastguard Workerclass RelationshipType: 84*9e94795aSAndroid Build Coastguard Worker DESCRIBES = 'DESCRIBES' 85*9e94795aSAndroid Build Coastguard Worker VARIANT_OF = 'VARIANT_OF' 86*9e94795aSAndroid Build Coastguard Worker GENERATED_FROM = 'GENERATED_FROM' 87*9e94795aSAndroid Build Coastguard Worker CONTAINS = 'CONTAINS' 88*9e94795aSAndroid Build Coastguard Worker STATIC_LINK = 'STATIC_LINK' 89*9e94795aSAndroid Build Coastguard Worker 90*9e94795aSAndroid Build Coastguard Worker 91*9e94795aSAndroid Build Coastguard Worker@dataclass(frozen=True) 92*9e94795aSAndroid Build Coastguard Workerclass Relationship: 93*9e94795aSAndroid Build Coastguard Worker id1: str 94*9e94795aSAndroid Build Coastguard Worker relationship: RelationshipType 95*9e94795aSAndroid Build Coastguard Worker id2: str 96*9e94795aSAndroid Build Coastguard Worker 97*9e94795aSAndroid Build Coastguard Worker 98*9e94795aSAndroid Build Coastguard Worker@dataclass(frozen=True) 99*9e94795aSAndroid Build Coastguard Workerclass DocumentExternalReference: 100*9e94795aSAndroid Build Coastguard Worker id: str 101*9e94795aSAndroid Build Coastguard Worker uri: str 102*9e94795aSAndroid Build Coastguard Worker checksum: str 103*9e94795aSAndroid Build Coastguard Worker 104*9e94795aSAndroid Build Coastguard Worker 105*9e94795aSAndroid Build Coastguard Worker@dataclass(frozen=True) 106*9e94795aSAndroid Build Coastguard Workerclass License: 107*9e94795aSAndroid Build Coastguard Worker id: str 108*9e94795aSAndroid Build Coastguard Worker text: str 109*9e94795aSAndroid Build Coastguard Worker name: str 110*9e94795aSAndroid Build Coastguard Worker 111*9e94795aSAndroid Build Coastguard Worker 112*9e94795aSAndroid Build Coastguard Worker@dataclass 113*9e94795aSAndroid Build Coastguard Workerclass Document: 114*9e94795aSAndroid Build Coastguard Worker name: str 115*9e94795aSAndroid Build Coastguard Worker namespace: str 116*9e94795aSAndroid Build Coastguard Worker id: str = SPDXID_DOC 117*9e94795aSAndroid Build Coastguard Worker describes: str = SPDXID_PRODUCT 118*9e94795aSAndroid Build Coastguard Worker creators: List[str] = field(default_factory=list) 119*9e94795aSAndroid Build Coastguard Worker created: str = None 120*9e94795aSAndroid Build Coastguard Worker external_refs: List[DocumentExternalReference] = field(default_factory=list) 121*9e94795aSAndroid Build Coastguard Worker packages: List[Package] = field(default_factory=list) 122*9e94795aSAndroid Build Coastguard Worker files: List[File] = field(default_factory=list) 123*9e94795aSAndroid Build Coastguard Worker relationships: List[Relationship] = field(default_factory=list) 124*9e94795aSAndroid Build Coastguard Worker licenses: List[License] = field(default_factory=list) 125*9e94795aSAndroid Build Coastguard Worker 126*9e94795aSAndroid Build Coastguard Worker def add_external_ref(self, external_ref): 127*9e94795aSAndroid Build Coastguard Worker if not any(external_ref.uri == ref.uri for ref in self.external_refs): 128*9e94795aSAndroid Build Coastguard Worker self.external_refs.append(external_ref) 129*9e94795aSAndroid Build Coastguard Worker 130*9e94795aSAndroid Build Coastguard Worker def add_package(self, package): 131*9e94795aSAndroid Build Coastguard Worker p = next((p for p in self.packages if package.id == p.id), None) 132*9e94795aSAndroid Build Coastguard Worker if not p: 133*9e94795aSAndroid Build Coastguard Worker self.packages.append(package) 134*9e94795aSAndroid Build Coastguard Worker else: 135*9e94795aSAndroid Build Coastguard Worker for license_id in package.declared_license_ids: 136*9e94795aSAndroid Build Coastguard Worker if license_id not in p.declared_license_ids: 137*9e94795aSAndroid Build Coastguard Worker p.declared_license_ids.append(license_id) 138*9e94795aSAndroid Build Coastguard Worker 139*9e94795aSAndroid Build Coastguard Worker def add_relationship(self, rel): 140*9e94795aSAndroid Build Coastguard Worker if not any(rel.id1 == r.id1 and rel.id2 == r.id2 and rel.relationship == r.relationship 141*9e94795aSAndroid Build Coastguard Worker for r in self.relationships): 142*9e94795aSAndroid Build Coastguard Worker self.relationships.append(rel) 143*9e94795aSAndroid Build Coastguard Worker 144*9e94795aSAndroid Build Coastguard Worker def add_license(self, license): 145*9e94795aSAndroid Build Coastguard Worker if not any(license.id == l.id for l in self.licenses): 146*9e94795aSAndroid Build Coastguard Worker self.licenses.append(license) 147*9e94795aSAndroid Build Coastguard Worker 148*9e94795aSAndroid Build Coastguard Worker def generate_packages_verification_code(self): 149*9e94795aSAndroid Build Coastguard Worker for package in self.packages: 150*9e94795aSAndroid Build Coastguard Worker if not package.file_ids: 151*9e94795aSAndroid Build Coastguard Worker continue 152*9e94795aSAndroid Build Coastguard Worker 153*9e94795aSAndroid Build Coastguard Worker checksums = [] 154*9e94795aSAndroid Build Coastguard Worker for file in self.files: 155*9e94795aSAndroid Build Coastguard Worker if file.id in package.file_ids: 156*9e94795aSAndroid Build Coastguard Worker checksums.append(file.checksum.split(': ')[1]) 157*9e94795aSAndroid Build Coastguard Worker checksums.sort() 158*9e94795aSAndroid Build Coastguard Worker h = hashlib.sha1() 159*9e94795aSAndroid Build Coastguard Worker h.update(''.join(checksums).encode(encoding='utf-8')) 160*9e94795aSAndroid Build Coastguard Worker package.verification_code = h.hexdigest() 161*9e94795aSAndroid Build Coastguard Worker 162*9e94795aSAndroid Build Coastguard Workerdef encode_for_spdxid(s): 163*9e94795aSAndroid Build Coastguard Worker """Simple encode for string values used in SPDXID which uses the charset of A-Za-Z0-9.-""" 164*9e94795aSAndroid Build Coastguard Worker result = '' 165*9e94795aSAndroid Build Coastguard Worker for c in s: 166*9e94795aSAndroid Build Coastguard Worker if c.isalnum() or c in '.-': 167*9e94795aSAndroid Build Coastguard Worker result += c 168*9e94795aSAndroid Build Coastguard Worker elif c in '_@/': 169*9e94795aSAndroid Build Coastguard Worker result += '-' 170*9e94795aSAndroid Build Coastguard Worker else: 171*9e94795aSAndroid Build Coastguard Worker result += '0x' + c.encode('utf-8').hex() 172*9e94795aSAndroid Build Coastguard Worker 173*9e94795aSAndroid Build Coastguard Worker return result.lstrip('-')