Coverage for turvallisuusneuvonta/csaf/product.py: 75.89%
127 statements
« prev ^ index » next coverage.py v7.0.3, created at 2023-01-07 19:14 +0100
« prev ^ index » next coverage.py v7.0.3, created at 2023-01-07 19:14 +0100
1"""CSAF Product Tree model."""
2from __future__ import annotations
4import re
5from collections.abc import Sequence
6from enum import Enum
7from typing import Annotated, List, Optional, no_type_check
9from pydantic import BaseModel, Field, validator
11from turvallisuusneuvonta.csaf.definitions import (
12 AnyUrl,
13 Products,
14 ReferenceTokenForProductGroupInstance,
15 ReferenceTokenForProductInstance,
16)
19class FileHash(BaseModel):
20 """
21 Contains one hash value and algorithm of the file to be identified.
22 """
24 algorithm: Annotated[
25 str,
26 Field(
27 description='Contains the name of the cryptographic hash algorithm used to calculate the value.',
28 examples=['blake2b512', 'sha256', 'sha3-512', 'sha384', 'sha512'],
29 min_length=1,
30 title='Algorithm of the cryptographic hash',
31 ),
32 ]
33 value: Annotated[
34 str,
35 Field(
36 description='Contains the cryptographic hash value in hexadecimal representation.',
37 examples=[
38 (
39 '37df33cb7464da5c7f077f4d56a32bc84987ec1d85b234537c1c1a4d4fc8d09d'
40 'c29e2e762cb5203677bf849a2855a0283710f1f5fe1d6ce8d5ac85c645d0fcb3'
41 ),
42 '4775203615d9534a8bfca96a93dc8b461a489f69124a130d786b42204f3341cc',
43 '9ea4c8200113d49d26505da0e02e2f49055dc078d1ad7a419b32e291c7afebbb84badfbd46dec42883bea0b2a1fa697c',
44 ],
45 min_length=32,
46 regex='^[0-9a-fA-F]{32,}$',
47 title='Value of the cryptographic hash',
48 ),
49 ]
52class CryptographicHashes(BaseModel):
53 """
54 Contains all information to identify a file based on its cryptographic hash values.
55 """
57 file_hashes: Annotated[
58 Sequence[FileHash],
59 Field(
60 description='Contains a list of cryptographic hashes for this file.',
61 # min_items=1,
62 title='List of file hashes',
63 ),
64 ]
65 filename: Annotated[
66 str,
67 Field(
68 description='Contains the name of the file which is identified by the hash values.',
69 examples=['WINWORD.EXE', 'msotadddin.dll', 'sudoers.so'],
70 # min_length=1,
71 title='Filename',
72 ),
73 ]
75 @no_type_check
76 @validator('file_hashes', 'filename')
77 @classmethod
78 def check_len(cls, v):
79 if not v:
80 raise ValueError('mandatory element present but empty')
81 return v
84class GenericUri(BaseModel):
85 """
86 Provides a generic extension point for any identifier which is either vendor-specific or
87 derived from a standard not yet supported.
88 """
90 namespace: Annotated[
91 AnyUrl,
92 Field(
93 description=(
94 'Refers to a URL which provides the name and knowledge about the specification used or'
95 ' is the namespace in which these values are valid.'
96 ),
97 title='Namespace of the generic URI',
98 ),
99 ]
100 uri: Annotated[AnyUrl, Field(description='Contains the identifier itself.', title='URI')]
103class SerialNumber(BaseModel):
104 __root__: Annotated[
105 str,
106 Field(
107 description='Contains a part, or a full serial number of the component to identify.',
108 min_length=1,
109 title='Serial number',
110 ),
111 ]
114class StockKeepingUnit(BaseModel):
115 __root__: Annotated[
116 str,
117 Field(
118 description=(
119 'Contains a part, or a full stock keeping unit (SKU) which is used in the ordering process'
120 ' to identify the component.'
121 ),
122 min_length=1,
123 title='Stock keeping unit',
124 ),
125 ]
128class HelperToIdentifyTheProduct(BaseModel):
129 """
130 Provides at least one method which aids in identifying the product in an asset database.
131 """
133 cpe: Annotated[
134 Optional[str],
135 Field(
136 description=(
137 'The Common Platform Enumeration (CPE) attribute refers to a method for naming platforms external'
138 ' to this specification.'
139 ),
140 min_length=5,
141 regex=(
142 '^(cpe:2\\.3:[aho\\*\\-](:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|'
143 '(\\\\[\\\\\\*\\?!"#\\$%&\'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|\\}~]))+(\\?*|\\*?))|[\\*\\-])){5}'
144 '(:(([a-zA-Z]{2,3}(-([a-zA-Z]{2}|[0-9]{3}))?)|[\\*\\-]))(:(((\\?*|\\*?)([a-zA-Z0-9\\-\\._]|'
145 '(\\\\[\\\\\\*\\?!"#\\$%&\'\\(\\)\\+,/:;<=>@\\[\\]\\^`\\{\\|\\}~]))+(\\?*|\\*?))|[\\*\\-])){4})|'
146 '([c][pP][eE]:/[AHOaho]?(:[A-Za-z0-9\\._\\-~%]*){0,6})$'
147 ),
148 title='Common Platform Enumeration representation',
149 ),
150 ] = None
151 hashes: Annotated[
152 Optional[Sequence[CryptographicHashes]],
153 Field(
154 description='Contains a list of cryptographic hashes usable to identify files.',
155 # min_items=1,
156 title='List of hashes',
157 ),
158 ] = None
159 purl: Annotated[
160 Optional[AnyUrl],
161 Field(
162 description=(
163 'The package URL (purl) attribute refers to a method for reliably identifying and'
164 ' locating software packages external to this specification.'
165 ),
166 # min_length=7,
167 # regex='^pkg:[A-Za-z\\.\\-\\+][A-Za-z0-9\\.\\-\\+]*/.+',
168 title='package URL representation',
169 ),
170 ] = None
171 sbom_urls: Annotated[
172 Optional[Sequence[AnyUrl]],
173 Field(
174 description='Contains a list of URLs where SBOMs for this product can be retrieved.',
175 # min_items=1,
176 title='List of SBOM URLs',
177 ),
178 ] = None
179 serial_numbers: Annotated[
180 Optional[Sequence[SerialNumber]],
181 Field(
182 description='Contains a list of parts, or full serial numbers.',
183 # min_items=1,
184 title='List of serial numbers',
185 ),
186 ] = None
187 skus: Annotated[
188 Optional[Sequence[StockKeepingUnit]],
189 Field(
190 description='Contains a list of parts, or full stock keeping units.',
191 # min_items=1,
192 title='List of stock keeping units',
193 ),
194 ] = None
195 x_generic_uris: Annotated[
196 Optional[Sequence[GenericUri]],
197 Field(
198 description=(
199 'Contains a list of identifiers which are either vendor-specific or derived from'
200 ' a standard not yet supported.'
201 ),
202 # min_items=1,
203 title='List of generic URIs',
204 ),
205 ] = None
207 @no_type_check
208 @validator('hashes', 'sbom_urls', 'serial_numbers', 'skus', 'x_generic_uris')
209 @classmethod
210 def check_len(cls, v):
211 if not v:
212 raise ValueError('optional element present but empty')
213 return v
215 @no_type_check
216 @validator('purl')
217 @classmethod
218 def check_purl(cls, v):
219 if not v or len(v) < 7:
220 raise ValueError('optional purl element present but too short')
221 if not re.match('^pkg:[A-Za-z\\.\\-\\+][A-Za-z0-9\\.\\-\\+]*/.+', v):
222 raise ValueError('optional purl element present but is no purl (regex does not match)')
223 return v
226class FullProductName(BaseModel):
227 """
228 Specifies information about the product and assigns the product_id.
229 """
231 name: Annotated[
232 str,
233 Field(
234 description=(
235 "The value should be the product's full canonical name, including version number and other attributes,"
236 ' as it would be used in a human-friendly document.'
237 ),
238 examples=[
239 'Cisco AnyConnect Secure Mobility Client 2.3.185',
240 'Microsoft Host Integration Server 2006 Service Pack 1',
241 ],
242 min_length=1,
243 title='Textual description of the product',
244 ),
245 ]
246 product_id: ReferenceTokenForProductInstance
247 product_identification_helper: Annotated[
248 Optional[HelperToIdentifyTheProduct],
249 Field(
250 description='Provides at least one method which aids in identifying the product in an asset database.',
251 title='Helper to identify the product',
252 ),
253 ] = None
256class ProductGroup(BaseModel):
257 """
258 Defines a new logical group of products that can then be referred to in other parts of the document to address
259 a group of products with a single identifier.
260 """
262 group_id: ReferenceTokenForProductGroupInstance
263 product_ids: Annotated[
264 Sequence[ReferenceTokenForProductInstance],
265 Field(
266 description='Lists the product_ids of those products which known as one group in the document.',
267 # min_items=2,
268 title='List of Product IDs',
269 ),
270 ]
271 summary: Annotated[
272 Optional[str],
273 Field(
274 description='Gives a short, optional description of the group.',
275 examples=[
276 'Products supporting Modbus.',
277 'The x64 versions of the operating system.',
278 ],
279 min_length=1,
280 title='Summary of the product group',
281 ),
282 ] = None
284 @no_type_check
285 @validator('product_ids')
286 @classmethod
287 def check_len(cls, v):
288 if len(v) < 2:
289 raise ValueError('mandatory element present but too few items')
290 return v
293class ProductStatus(BaseModel):
294 """
295 Contains different lists of product_ids which provide details on the status of the referenced product related
296 to the current vulnerability.
297 """
299 first_affected: Annotated[
300 Optional[Products],
301 Field(
302 description='These are the first versions of the releases known to be affected by the vulnerability.',
303 title='First affected',
304 ),
305 ]
306 first_fixed: Annotated[
307 Optional[Products],
308 Field(
309 description='These versions contain the first fix for the vulnerability but may not be the recommended'
310 ' fixed versions.',
311 title='First fixed',
312 ),
313 ]
314 fixed: Annotated[
315 Optional[Products],
316 Field(
317 description='These versions contain a fix for the vulnerability but may not be the recommended'
318 ' fixed versions.',
319 title='Fixed',
320 ),
321 ]
322 known_affected: Annotated[
323 Optional[Products],
324 Field(
325 description='These versions are known to be affected by the vulnerability.',
326 title='Known affected',
327 ),
328 ]
329 known_not_affected: Annotated[
330 Optional[Products],
331 Field(
332 description='These versions are known not to be affected by the vulnerability.',
333 title='Known not affected',
334 ),
335 ]
336 last_affected: Annotated[
337 Optional[Products],
338 Field(
339 description='These are the last versions in a release train known to be affected by the vulnerability.'
340 ' Subsequently released versions would contain a fix for the vulnerability.',
341 title='Last affected',
342 ),
343 ]
344 recommended: Annotated[
345 Optional[Products],
346 Field(
347 description='These versions have a fix for the vulnerability and are the vendor-recommended versions for'
348 ' fixing the vulnerability.',
349 title='Recommended',
350 ),
351 ]
352 under_investigation: Annotated[
353 Optional[Products],
354 Field(
355 description='It is not known yet whether these versions are or are not affected by the vulnerability.'
356 ' However, it is still under investigation - the result will be provided in a later release'
357 ' of the document.',
358 title='Under investigation',
359 ),
360 ]
363class RelationshipCategory(Enum):
364 """
365 Defines the category of relationship for the referenced component.
366 """
368 default_component_of = 'default_component_of'
369 external_component_of = 'external_component_of'
370 installed_on = 'installed_on'
371 installed_with = 'installed_with'
372 optional_component_of = 'optional_component_of'
375class Relationship(BaseModel):
376 """
377 Establishes a link between two existing full_product_name_t elements, allowing the document producer to define
378 a combination of two products that form a new full_product_name entry.
379 """
381 category: Annotated[
382 RelationshipCategory,
383 Field(
384 description='Defines the category of relationship for the referenced component.',
385 title='Relationship category',
386 ),
387 ]
388 full_product_name: FullProductName
389 product_reference: Annotated[
390 ReferenceTokenForProductInstance,
391 Field(
392 description=(
393 'Holds a Product ID that refers to the Full Product Name element,'
394 ' which is referenced as the first element of the relationship.'
395 ),
396 title='Product reference',
397 ),
398 ]
399 relates_to_product_reference: Annotated[
400 ReferenceTokenForProductInstance,
401 Field(
402 description=(
403 'Holds a Product ID that refers to the Full Product Name element,'
404 ' which is referenced as the second element of the relationship.'
405 ),
406 title='Relates to product reference',
407 ),
408 ]
411class ProductTree(BaseModel):
412 """
413 Is a container for all fully qualified product names that can be referenced elsewhere in the document.
414 """
416 branches: Optional[Branches]
417 full_product_names: Annotated[
418 Optional[List[FullProductName]],
419 Field(
420 description='Contains a list of full product names.',
421 min_items=1,
422 title='List of full product names',
423 ),
424 ]
425 product_groups: Annotated[
426 Optional[List[ProductGroup]],
427 Field(
428 description='Contains a list of product groups.',
429 min_items=1,
430 title='List of product groups',
431 ),
432 ]
433 relationships: Annotated[
434 Optional[List[Relationship]],
435 Field(
436 description='Contains a list of relationships.',
437 min_items=1,
438 title='List of relationships',
439 ),
440 ]
442 @no_type_check
443 @validator('full_product_names', 'product_groups', 'relationships')
444 @classmethod
445 def check_len(cls, v):
446 if not v:
447 raise ValueError('optional element present but empty')
448 return v
451class BranchCategory(Enum):
452 """
453 Describes the characteristics of the labeled branch.
454 """
456 architecture = 'architecture'
457 host_name = 'host_name'
458 language = 'language'
459 legacy = 'legacy'
460 patch_level = 'patch_level'
461 product_family = 'product_family'
462 product_name = 'product_name'
463 product_version = 'product_version'
464 service_pack = 'service_pack'
465 specification = 'specification'
466 vendor = 'vendor'
469class Branch(BaseModel):
470 """
471 Is a part of the hierarchical structure of the product tree.
472 """
474 branches: Optional[Branches]
475 category: Annotated[
476 BranchCategory,
477 Field(
478 description='Describes the characteristics of the labeled branch.',
479 title='Category of the branch',
480 ),
481 ]
482 name: Annotated[
483 str,
484 Field(
485 description="Contains the canonical descriptor or 'friendly name' of the branch.",
486 examples=[
487 '10',
488 '365',
489 'Microsoft',
490 'Office',
491 'PCS 7',
492 'SIMATIC',
493 'Siemens',
494 'Windows',
495 ],
496 min_length=1,
497 title='Name of the branch',
498 ),
499 ]
500 product: Optional[FullProductName]
503class Branches(BaseModel):
504 """
505 Contains branch elements as children of the current element.
506 """
508 __root__: Annotated[
509 List[Branch],
510 Field(
511 description='Contains branch elements as children of the current element.',
512 min_items=1,
513 title='List of branches',
514 ),
515 ]
517 @no_type_check
518 @validator('__root__')
519 @classmethod
520 def check_len(cls, v):
521 if not v:
522 raise ValueError('mandatory element present but empty')
523 return v
526Branch.update_forward_refs()
527FullProductName.update_forward_refs()
528ProductTree.update_forward_refs()