Wei Li | dec97b1 | 2023-04-07 16:45:17 -0700 | [diff] [blame] | 1 | #!/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 | """ |
| 18 | Define data classes that model SBOMs defined by SPDX. The data classes could be |
| 19 | written out to different formats (tagvalue, JSON, etc) of SPDX with corresponding |
| 20 | writer utilities. |
| 21 | |
| 22 | Rrefer to SPDX 2.3 spec: https://spdx.github.io/spdx-spec/v2.3/ and go/android-spdx for details of |
| 23 | fields in each data class. |
| 24 | """ |
| 25 | |
| 26 | from dataclasses import dataclass, field |
| 27 | from typing import List |
Wei Li | d263695 | 2023-05-30 15:03:03 -0700 | [diff] [blame] | 28 | import hashlib |
Wei Li | dec97b1 | 2023-04-07 16:45:17 -0700 | [diff] [blame] | 29 | |
| 30 | SPDXID_DOC = 'SPDXRef-DOCUMENT' |
| 31 | SPDXID_PRODUCT = 'SPDXRef-PRODUCT' |
| 32 | SPDXID_PLATFORM = 'SPDXRef-PLATFORM' |
| 33 | |
| 34 | PACKAGE_NAME_PRODUCT = 'PRODUCT' |
| 35 | PACKAGE_NAME_PLATFORM = 'PLATFORM' |
| 36 | |
Wei Li | 5290825 | 2023-04-14 18:49:42 -0700 | [diff] [blame] | 37 | VALUE_NOASSERTION = 'NOASSERTION' |
| 38 | VALUE_NONE = 'NONE' |
| 39 | |
Wei Li | dec97b1 | 2023-04-07 16:45:17 -0700 | [diff] [blame] | 40 | |
| 41 | class PackageExternalRefCategory: |
| 42 | SECURITY = 'SECURITY' |
| 43 | PACKAGE_MANAGER = 'PACKAGE-MANAGER' |
| 44 | PERSISTENT_ID = 'PERSISTENT-ID' |
| 45 | OTHER = 'OTHER' |
| 46 | |
| 47 | |
| 48 | class PackageExternalRefType: |
| 49 | cpe22Type = 'cpe22Type' |
| 50 | cpe23Type = 'cpe23Type' |
| 51 | |
| 52 | |
| 53 | @dataclass |
| 54 | class PackageExternalRef: |
| 55 | category: PackageExternalRefCategory |
| 56 | type: PackageExternalRefType |
| 57 | locator: str |
| 58 | |
| 59 | |
| 60 | @dataclass |
| 61 | class 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 |
| 74 | class File: |
| 75 | id: str |
| 76 | name: str |
| 77 | checksum: str |
| 78 | |
| 79 | |
| 80 | class RelationshipType: |
| 81 | DESCRIBES = 'DESCRIBES' |
| 82 | VARIANT_OF = 'VARIANT_OF' |
| 83 | GENERATED_FROM = 'GENERATED_FROM' |
Wei Li | fd7e651 | 2023-05-05 10:49:28 -0700 | [diff] [blame] | 84 | CONTAINS = 'CONTAINS' |
Wei Li | d263695 | 2023-05-30 15:03:03 -0700 | [diff] [blame] | 85 | STATIC_LINK = 'STATIC_LINK' |
Wei Li | dec97b1 | 2023-04-07 16:45:17 -0700 | [diff] [blame] | 86 | |
| 87 | |
| 88 | @dataclass |
| 89 | class Relationship: |
| 90 | id1: str |
| 91 | relationship: RelationshipType |
| 92 | id2: str |
| 93 | |
| 94 | |
| 95 | @dataclass |
| 96 | class DocumentExternalReference: |
| 97 | id: str |
| 98 | uri: str |
| 99 | checksum: str |
| 100 | |
| 101 | |
| 102 | @dataclass |
| 103 | class 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 Li | d263695 | 2023-05-30 15:03:03 -0700 | [diff] [blame] | 127 | |
| 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 Li | f99db99 | 2023-07-31 14:12:52 -0700 | [diff] [blame] | 136 | checksums.append(file.checksum.split(': ')[1]) |
Wei Li | d263695 | 2023-05-30 15:03:03 -0700 | [diff] [blame] | 137 | checksums.sort() |
| 138 | h = hashlib.sha1() |
| 139 | h.update(''.join(checksums).encode(encoding='utf-8')) |
| 140 | package.verification_code = h.hexdigest() |
Wei Li | c134b76 | 2023-10-17 23:52:30 -0700 | [diff] [blame] | 141 | |
| 142 | def 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('-') |