xref: /aosp_15_r20/build/make/tools/sbom/sbom_data.py (revision 9e94795a3d4ef5c1d47486f9a02bb378756cea8a)
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('-')