blob: b5ac8a59c32abf408db851cd82afd731f68b4b33 [file] [log] [blame]
Wei Lidec97b12023-04-07 16:45:17 -07001#!/usr/bin/env python3
2#
3# Copyright (C) 2023 The Android Open Source Project
4#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17"""
18Define data classes that model SBOMs defined by SPDX. The data classes could be
19written out to different formats (tagvalue, JSON, etc) of SPDX with corresponding
20writer utilities.
21
22Rrefer to SPDX 2.3 spec: https://spdx.github.io/spdx-spec/v2.3/ and go/android-spdx for details of
23fields in each data class.
24"""
25
26from dataclasses import dataclass, field
27from typing import List
Wei Lid2636952023-05-30 15:03:03 -070028import hashlib
Wei Lidec97b12023-04-07 16:45:17 -070029
30SPDXID_DOC = 'SPDXRef-DOCUMENT'
31SPDXID_PRODUCT = 'SPDXRef-PRODUCT'
32SPDXID_PLATFORM = 'SPDXRef-PLATFORM'
33
34PACKAGE_NAME_PRODUCT = 'PRODUCT'
35PACKAGE_NAME_PLATFORM = 'PLATFORM'
36
Wei Li52908252023-04-14 18:49:42 -070037VALUE_NOASSERTION = 'NOASSERTION'
38VALUE_NONE = 'NONE'
39
Wei Lidec97b12023-04-07 16:45:17 -070040
41class PackageExternalRefCategory:
42 SECURITY = 'SECURITY'
43 PACKAGE_MANAGER = 'PACKAGE-MANAGER'
44 PERSISTENT_ID = 'PERSISTENT-ID'
45 OTHER = 'OTHER'
46
47
48class PackageExternalRefType:
49 cpe22Type = 'cpe22Type'
50 cpe23Type = 'cpe23Type'
51
52
53@dataclass
54class PackageExternalRef:
55 category: PackageExternalRefCategory
56 type: PackageExternalRefType
57 locator: str
58
59
60@dataclass
61class Package:
62 name: str
63 id: str
64 version: str = None
65 supplier: str = None
66 download_location: str = None
67 files_analyzed: bool = False
68 verification_code: str = None
69 file_ids: List[str] = field(default_factory=list)
70 external_refs: List[PackageExternalRef] = field(default_factory=list)
71
72
73@dataclass
74class File:
75 id: str
76 name: str
77 checksum: str
78
79
80class RelationshipType:
81 DESCRIBES = 'DESCRIBES'
82 VARIANT_OF = 'VARIANT_OF'
83 GENERATED_FROM = 'GENERATED_FROM'
Wei Lifd7e6512023-05-05 10:49:28 -070084 CONTAINS = 'CONTAINS'
Wei Lid2636952023-05-30 15:03:03 -070085 STATIC_LINK = 'STATIC_LINK'
Wei Lidec97b12023-04-07 16:45:17 -070086
87
88@dataclass
89class Relationship:
90 id1: str
91 relationship: RelationshipType
92 id2: str
93
94
95@dataclass
96class DocumentExternalReference:
97 id: str
98 uri: str
99 checksum: str
100
101
102@dataclass
103class Document:
104 name: str
105 namespace: str
106 id: str = SPDXID_DOC
107 describes: str = SPDXID_PRODUCT
108 creators: List[str] = field(default_factory=list)
109 created: str = None
110 external_refs: List[DocumentExternalReference] = field(default_factory=list)
111 packages: List[Package] = field(default_factory=list)
112 files: List[File] = field(default_factory=list)
113 relationships: List[Relationship] = field(default_factory=list)
114
115 def add_external_ref(self, external_ref):
116 if not any(external_ref.uri == ref.uri for ref in self.external_refs):
117 self.external_refs.append(external_ref)
118
119 def add_package(self, package):
120 if not any(package.id == p.id for p in self.packages):
121 self.packages.append(package)
122
123 def add_relationship(self, rel):
124 if not any(rel.id1 == r.id1 and rel.id2 == r.id2 and rel.relationship == r.relationship
125 for r in self.relationships):
126 self.relationships.append(rel)
Wei Lid2636952023-05-30 15:03:03 -0700127
128 def generate_packages_verification_code(self):
129 for package in self.packages:
130 if not package.file_ids:
131 continue
132
133 checksums = []
134 for file in self.files:
135 if file.id in package.file_ids:
Wei Lif99db992023-07-31 14:12:52 -0700136 checksums.append(file.checksum.split(': ')[1])
Wei Lid2636952023-05-30 15:03:03 -0700137 checksums.sort()
138 h = hashlib.sha1()
139 h.update(''.join(checksums).encode(encoding='utf-8'))
140 package.verification_code = h.hexdigest()
Wei Lic134b762023-10-17 23:52:30 -0700141
142def encode_for_spdxid(s):
143 """Simple encode for string values used in SPDXID which uses the charset of A-Za-Z0-9.-"""
144 result = ''
145 for c in s:
146 if c.isalnum() or c in '.-':
147 result += c
148 elif c in '_@/':
149 result += '-'
150 else:
151 result += '0x' + c.encode('utf-8').hex()
152
153 return result.lstrip('-')